Post Engineer

io.github.Ryan-CarlosoPublished by GitHub account Ryan-Carlosov1.32.0Updated Nov 6, 2026

Create AI video personas and schedule videos to YouTube, Instagram, LinkedIn and Bluesky.

Overview

AI-generated overview

Creates AI video personas and generates short videos that are scheduled and auto-published to YouTube, Instagram, LinkedIn and Bluesky.

What you can do
  • Create and manage personas: build characters with avatar, voice, language and niche, edit them, and curate a per-persona image library
  • Pick voices and faces: browse stock voices and faces, register and preview cloned voices, and delete clones
  • Generate and validate videos: validate a segmented script without spending tokens, generate a persona video, poll progress, fetch final URLs, and cancel tasks
  • Schedule and edit posts: inspect upcoming and past slots, rename or delete pending slots, and cancel whole schedules
  • Connect and manage social accounts: list accounts, connect YouTube, Instagram, LinkedIn or Bluesky, set per-persona default publishing accounts, and disconnect
  • Track costs and…
When to use it
Useful when you want an assistant to run a faceless or presenter-style short-video pipeline end to end: draft and validate a script, generate the video, and queue it for publishing on connected social accounts. Also handy for auditing token spend and post performance from the same session.
Requirements
Runs locally via npx as the npm package post-engineer-mcp, so Node.js is needed. Requires a Post Engineer API key in the POST_ENGINEER_API_KEY environment variable, generated on the hosted platform; POST_ENGINEER_API_URL can point at a self-hosted instance. Social publishing requires accounts connected through the platform's OAuth flow or a Bluesky app password. Video generation consumes prepaid tokens.
Before you install
Generation spends prepaid tokens and every generation is tied to an automatic publish, so a call can post publicly without a separate confirmation step. Deleting a persona is irreversible and forfeits tokens for pending slots. Connecting Bluesky asks for an app password, and OAuth connections grant publishing rights to the assistant. Uploaded media is stored as a temporary engine asset with a one-hour lifetime.

Installation

In SourceWeft

  1. Open Post Engineer in the dashboard and add it to a workspace.
  2. Enable the server for the chats that should use its tools.

Desktop only via STDIO. STDIO servers start a local process, so they need the SourceWeft desktop host.

Other MCP clients

Follow the launch instructions in the repository.

README

Post Engineer

[CI] [License: Apache 2.0] [npm]

🇧🇷🇵🇹 Leia em Português

Create AI video personas that generate and publish short videos on autopilot.

Give a persona a niche and a voice; Post Engineer writes the script, generates the voiceover, assembles the video, and publishes it to your social accounts on a schedule. Manage everything from the web dashboard, drive it programmatically through the API, or let your AI agent run it through the MCP server — all from this one monorepo.

Post Engineer is open source under the Apache License, Version 2.0. Self-host the whole platform on your own VPS following docs/SELF_HOSTING.md, or use the hosted version and buy tokens. Lip sync (LongCat) runs on Modal.com: Modal's Starter plan includes $30/month in compute credits (see modal.com/pricing). We are not Modal: this free-credit offer is theirs and can end at any time. Light self-hosted usage can fit inside the free credits while it lasts — usage beyond that is billed by Modal.

Prefer the hosted version? The same platform runs at post-engineer.com — you can skip the local setup and connect the MCP server straight to your account (see MCP server).

What it does

  • AI personas — persistent characters with a voice, face, language, and niche. Optional lip-synced intro clips for a human-like presenter, plus a persona image library for face consistency across videos.
  • Automatic video generation — topic in, HD short video out: script (LLM), voiceover (TTS), subtitles, stock footage, and background music, assembled into a finished video.
  • Scheduling & publishing — queue posts for YouTube, Instagram, LinkedIn, and Bluesky; track upcoming and past posts with per-account status.
  • Dashboard — personas, posts history, connected social accounts, API keys, and token billing in one Next.js app.
  • Agent control — an MCP server (apps/mcp/) exposes the same capabilities to AI agents (Claude, Cursor, Codex, OpenCode): create personas, generate videos, check status, and schedule posts.

Repository layout

This is a pnpm/Nx monorepo — web, engine, and MCP live together:

text
apps/web/       Next.js 15 dashboard + API routes (TypeScript)apps/engine/    Python/FastAPI video-generation engineapps/mcp/       MCP server for AI agents (npm package: post-engineer-mcp)

The web app owns authentication, personas, scheduling, OAuth, billing, and the API. It calls the engine over HTTP (authenticated with a shared secret, MONEYPRINT_API_SECRET) for video generation, TTS, and publishing. The MCP server talks to the web API with a user API key. Postgres (via Supabase) is the system of record: personas, schedules, OAuth tokens (encrypted at rest), API keys, and token balances.

Prerequisites

  • Node.js 20+ and pnpm 10 (npm install -g pnpm or via corepack)
  • Python 3.11–3.12 and uv (curl -LsSf astral.sh/uv/install.sh | sh)
  • ffmpeg on PATH (the engine shells out to it via moviepy)
  • A Supabase project (hosted at supabase.com, or self-hosted) with a Postgres database
  • API keys for the external services you plan to use (see the table below)

Local setup from scratch

1. Install dependencies

bash
git clone https://github.com/Ryan-Carloso/post-engineer.gitcd post-engineerpnpm install

2. Set up Supabase

  1. Create a project at supabase.com (or point at your self-hosted instance).

  2. In the Supabase SQL editor, apply the database scripts. The persona image library script lives in this repo: supabase/migrations/002_persona-images.sql (manual-apply by design).

    Need help? If you are setting up a fresh project and need the current schema snapshot, reach out: Email: [email protected] · WhatsApp: +351 962 248 268

3. Create the three env files (never commit these)

There are exactly three env files by design — copy each from its example and fill in your own values:

bash
cp apps/web/.env.example apps/web/.envcp apps/engine/.env.example apps/engine/.envcp apps/engine/config.example.toml apps/engine/config.toml
FilePurposeKey variables
apps/web/.envWeb app + API routesNEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY (server-only), TOKEN_ENCRYPTION_KEY (generate: openssl rand -base64 32), MONEYPRINT_API_SECRET, MONEYPRINT_API_URL, OAuth client ids/secrets + redirect URIs, Stripe keys, MCP_OAUTH_PRIVATE_KEY_PEM
apps/engine/.envEngine runtimeMONEYPRINT_API_SECRET (must be identical to the web value), SUPABASE_URL + SUPABASE_SERVICE_ROLE_KEY (for the in-process fill scheduler), mandatory R2_ACCOUNT_ID + R2_ACCESS_KEY_ID + R2_SECRET_ACCESS_KEY (final-video archive), optional DISCORD_WEBHOOK_URL, optional POSTHOG_API_KEY (error tracking, analytics funnel, AI observability traces)
apps/engine/config.tomlEngine behavior configLLM/TTS/stock-footage provider keys (OpenAI-compatible, Pexels, Pixabay, …), listen_port (default 8080)

The app fails fast on missing variables (no silent fallbacks) — see apps/web/.env.example for the full documented list.

4. Register OAuth apps (for social publishing)

To connect YouTube, Instagram, or LinkedIn accounts you need OAuth apps whose redirect URIs exactly match the values in apps/web/.env:

  • Google / YouTube — Google Cloud Console: create an OAuth 2.0 Client ID, add the authorized redirect URIs from GOOGLE_REDIRECT_URI / GOOGLE_REDIRECT_URI_LOCAL, and fill in GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET.
  • Instagram — Meta for Developers: a Business app with the Instagram product (instagram_business_basic and instagram_business_content_publish permissions); set INSTAGRAM_CLIENT_ID / INSTAGRAM_CLIENT_SECRET and register INSTAGRAM_REDIRECT_URI.
  • LinkedIn — LinkedIn Developers: create an app, register LINKEDIN_REDIRECT_URI, and set LINKEDIN_CLIENT_ID / LINKEDIN_CLIENT_SECRET.
  • Threads — (Threads API). The "Access the Threads API" use case of the Meta app has its own App ID and App Secret (THREADS_CLIENT_ID / THREADS_CLIENT_SECRET), different from Instagram's. Add the Threads product (threads_basic, threads_content_publish and threads_manage_insights permissions) and register THREADS_REDIRECT_URI.
  • Bluesky uses an app password entered in the dashboard (encrypted at rest) — no OAuth app registration needed.

5. Stripe setup (token billing)

  1. Create a Stripe account and get the secret + publishable keys (STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY).
  2. Create the token-pack products and put the price IDs in STRIPE_PRICE_PACK_10 / _50 / _100.
  3. Add a webhook endpoint pointing at /api/billing/webhook and store the signing secret in STRIPE_WEBHOOK_SECRET. The webhook handler verifies the signature and is idempotent.

6. Generate the self-managed secrets

These are yours to create — nothing to register:

bash
# Web <-> engine shared secret (identical in both .env files)openssl rand -hex 32
# OAuth token encryption key (AES-256-GCM)openssl rand -base64 32
# MCP OAuth signing key (PEM, \n-escaped into MCP_OAUTH_PRIVATE_KEY_PEM)openssl ecparam -genkey -name prime256v1 -noout | openssl pkcs8 -topk8 -nocrypt

Running

bash
pnpm dev:web      # dashboard at http://localhost:3434pnpm dev:engine   # video engine API at http://127.0.0.1:8080 (see /docs there)

Useful commands (all verified against package.json / project.json):

bash
pnpm test        # all tests (web + engine + MCP, via Nx)pnpm lint        # ESLint (web + MCP) + ruff (engine)pnpm typecheck   # tsc --noEmit (web + MCP) + engine checkspnpm build       # production build of the web apppnpm graph       # Nx project graph

Per-app details: apps/web/README.md, apps/engine/README.md, apps/mcp/README.md.

MCP server

The MCP server (apps/mcp/, npm package post-engineer-mcp) lets AI agents create personas, generate videos, check generation status, and schedule posts on your account. You don't need to clone this repo to use it against the hosted platform:

json
{  "mcpServers": {    "post-engineer": {      "type": "local",      "command": ["npx", "-y", "post-engineer-mcp@latest"],      "environment": {        "POST_ENGINEER_API_KEY": "<MY_API_KEY>"      }    }  }}

Generate <MY_API_KEY> at post-engineer.com/recipes. The @latest tag matters: npx reuses its cached copy without checking for updates. To point the server at your own self-hosted instance instead, set POST_ENGINEER_API_URL (see apps/mcp/README.md for the full tool list, OpenCode timeout tips, and local development).

External services you must key yourself

ServiceRequired?What forWhere to get it
SupabaseYesPostgres, auth, storagesupabase.com or self-hosted
Google OAuth appFor YouTube publishingOAuth client id/secretGoogle Cloud Console
Instagram (Meta) appFor Instagram publishingOAuth client id/secretdevelopers.facebook.com
LinkedIn appFor LinkedIn publishingOAuth client id/secretdeveloper.linkedin.com
StripeFor token billingSecret/publishable keys, price IDs, webhook secretstripe.com
LLM provider key or OllamaFor script generationOpenAI-compatible key, or run Ollama locallyYour provider / ollama.com
Pexels / Pixabay / Coverr / TwelveLabsFor stock footageAPI keysRespective developer portals
MONEYPRINT_API_SECRETYesWeb ↔ engine authGenerate yourself (openssl rand -hex 32)
TOKEN_ENCRYPTION_KEYYesEncrypts OAuth tokens at restGenerate yourself (openssl rand -base64 32)
MCP_OAUTH_PRIVATE_KEY_PEMFor MCP server OAuthSigns MCP tokensGenerate yourself (EC key, see above)
PostHogOptionalError tracking + product analyticsNEXT_PUBLIC_POSTHOG_KEY (browser), POSTHOG_API_KEY (server), NEXT_PUBLIC_POSTHOG_HOST — see apps/web/.env.example
Discord webhookOptionalFill-schedule notificationsDiscord channel settings
ZAI_API_KEYCI onlyAI code-review workflowNot a runtime dependency

Support & Contact

Questions, get in touch:

License

Licensed under the Apache License, Version 2.0 — see CONTRIBUTING.md and SECURITY.md.

Credits

Video engine derived from MoneyPrinterTurbo (MIT © 2024 Harry), extended with persona lip-sync, background-music catalog, batch queue, scheduling, and publishing.

Source: README.md at commit 7beca2b

Tools

41

create_persona

Create a new AI persona with avatar, voice, language, and niche prompt. avatarUrl is REQUIRED: every persona has a face. A video without a face is a per-video choice — set options.faceless on generate_persona_video.

ParametersnamerequirednicheimagesvoiceIdrequiredlanguageavatarUrlrequiredfaceQualityvideoAspectvideoLayoutscriptPromptparagraphNumberimagePrimaryIndex

list_personas

List all existing personas for the authenticated user.

Takes no parameters.

list_voices

List persona voices (voiceId options): 65 English house voices, each with a text description of how it sounds plus gender/accent/kind, and your ready clones. Filter with gender, origin and search (e.g. "deep calm").

Parametersgenderoriginsearch

list_cloned_voices

List per-user Chatterbox voice clones (status, voice_id when ready).

Takes no parameters.

create_cloned_voice

Register a voice clone from a short HTTPS audio URL (requires explicit consent).

ParametersconsentrequireddenoiserequiredaudioUrlrequiredlanguagerequired

get_cloned_voice

Get one voice clone by id: its status (poll after create_cloned_voice until ready or failed) and voice_id once ready.

ParameterscloneIdrequired

delete_cloned_voice

Delete one of your voice clones by id.

ParameterscloneIdrequired

preview_cloned_voice

Synthesize a short MP3 preview for a ready voice clone, optionally speaking your own text (returns base64 audio; 10 previews per day).

ParameterstextcloneIdrequiredlanguage

list_faces

List default/stock persona faces (avatar options). Each face includes id, url, name, gender, age (single number, not a range), ethnicity, hair, and description in English so you can pick without seeing the photo. Pass a face url as avatarUrl when calling create_persona.

Takes no parameters.

update_persona

Update an existing AI persona (only the provided fields change).

ParametersnamenichevoiceIdlanguageavatarUrlpersonaIdrequiredvideoAspectvideoLayoutscriptPromptparagraphNumber

list_social_accounts

List connected social accounts (YouTube, Instagram, LinkedIn, Bluesky) with the account IDs needed for posting.

Takes no parameters.

get_persona_publishing_accounts

List a persona's default publishing accounts per provider. generate_persona_video falls back to these when no account IDs are passed; explicit IDs always win.

ParameterspersonaIdrequired

set_persona_publishing_accounts

Replace a persona's default publishing accounts (per provider). Account IDs come from list_social_accounts. Providers mapped to empty arrays keep no defaults.

ParametersaccountsrequiredpersonaIdrequired

connect_account

Connect a social account. For youtube/instagram/linkedin: returns an authorization URL — the user must open it in a browser and authorize, then the account connects automatically (verify with list_social_accounts). For bluesky: connects directly with handle + appPassword (app password, not the main account password).

ParametershandleproviderrequiredappPassword

list_schedules

List all automation schedules for the authenticated user.

Takes no parameters.

list_posts

List upcoming (scheduled) and past (published/failed) posts across all connected accounts. Returns two lists: upcoming slots and recent results, each with id, slot_at, status (awaiting|generating|ready|publishing|published|failed), title, schedule_id, task_id, progress (0-100: 0 for awaiting, 100 for ready/publishing/published, live engine progress for generating, last known for failed), stage (engine pipeline stage like "lipsync" while generating, "done" when complete, null otherwise), queuePosition/queueTotal (1-based position among the schedule's awaiting+generating slots; awaiting slots only, null otherwise). Failed slots also carry error and retryable (boolean: true when the failure looks transient). Use when the user asks about their posts — what is coming next, what already went out, how far a video has generated, where it sits in the queue, or why a post failed. Combine with list_schedules or list_social_accounts when persona/account names are needed.

Parameterslimit

cancel_schedule

Cancel (delete) an automation schedule by its schedule ID. Unpublished slots (pending, generating, or ready) are removed so they cannot publish, and their prepaid tokens are refunded. A post already publishing or already published is not recalled. Use list_schedules to find the ID.

ParametersscheduleIdrequired

get_slot

Get one scheduled post's full detail by slot ID: status, title, scheduled time, generation progress, its schedule (providers, publish mode) and persona. Use the slotId from list_posts.

ParametersslotIdrequired

update_slot_title

Edit the title text of one scheduled post. Only a slot that has not started generating (status pending/awaiting) can be edited — published or mid-flight slots are rejected.

ParameterstitlerequiredslotIdrequired

delete_slot

Delete one scheduled post slot. Only pending (awaiting) or failed slots can be deleted; published or mid-flight slots are rejected. The schedule's last slot cannot be deleted — use cancel_schedule to delete the whole schedule instead.

ParametersslotIdrequired

list_video_generations

List the video generation history (newest first): one row per generated video with status, error code, token refund flag, and timestamps. Use get_video_generation for one row's detail.

Parameterslimit

get_video_generation

Get one video generation's detail by generation ID (the generationId field from list_video_generations).

ParametersgenerationIdrequired

cancel_video_task

Cancel/remove one of your video tasks on the generation engine by task id (from get_video_status, get_video_task_progress, get_post or list_posts). Only the caller's own task can be cancelled; a persona-scoped API key can only cancel tasks belonging to its personas. Unknown or already-finished tasks return an error. No token refund is issued by this call.

ParameterstaskIdrequired

get_post

Get one post's full detail by id, whichever kind it is: a scheduled slot (kind "slot") or a direct video generation (kind "generation"). The server resolves the id, so there is no need to guess between get_slot and get_video_generation. Both kinds carry live generation progress while the video is still generating.

Parametersidrequired

get_post_metrics

Get per-video performance snapshots (views, likes, comments, saves) of published posts for one window (24h or 7d), newest first, optionally filtered by persona, account, provider (network) and script angle. Returns rows plus truncated (true when more rows exist than the server cap).

Parametersanglewindowrequiredaccountpersonaprovider

list_token_transactions

List the token ledger: every token credit and debit (purchases, generation spends, refunds), newest first, with amounts, reasons, and the related generation when there is one. Use to audit where tokens went.

Parameterslimitoffset

get_persona_delete_preview

Preview what deleting a persona would remove: counts of schedules, upcoming/published/failed slots, generated videos and library images, plus per-video download links (read-only, never mutates). Call before delete_persona.

ParameterspersonaIdrequired

delete_persona

DESTRUCTIVE and irreversible: delete a persona with all its schedules, slots, generated videos, and image library. No token refunds — prepaid tokens for pending slots are forfeited. Call get_persona_delete_preview first.

ParameterspersonaIdrequired

disconnect_account

Disconnect a social account by provider and account ID (the IDs from list_social_accounts). The account stops being available for publishing.

ParametersproviderrequiredproviderAccountIdrequired

get_token_balance

Get the prepaid token wallet balance. Check before triggering video generation, which costs tokens.

Takes no parameters.

generate_persona_video

Generate and schedule ONE persona video from a complete script. Product rule: generation is always tied to publishing — one call generates the video AND schedules its automatic publish; there is no orphan generation. Send the full script as segments (first hook, then body, last CTA; 60-200 words total, body at least 30, CTA 5-20, hook 15-40 words ending with terminal punctuation) — no LLM expands it. title is the slot label for this video. Pass providers plus per-provider account IDs (see list_social_accounts), startAt (ISO datetime, wall-clock in timezone when naive), times (HH:MM, 24h), and timezone. Set mode to 'asap' to publish the video the moment its generation finishes instead of scheduling it — startAt and times must NOT be set then. The slot must be 3h-30d ahead. idempotencyKey is generated automatically; reuse it to retry without generating twice (replayed: true). Poll the slot's taskId with get_video_task_progress.

ParametersmodetimestitlerequiredoptionsstartAtlanguagesegmentsrequiredtimezonepersonaIdprovidersrequiredidempotencyKeyblueskyAccountIds+3 more

validate_script

Validate a segments script mechanically, without spending tokens: hook first, CTA last, exactly one of each, no placeholders/links/markdown/emoji/stage directions, hook terminal punctuation, word limits (total 60-200, body at least 30, CTA 5-20, hook 15-40 words before TTS). Returns the word counts and the hook window (8-12 seconds spoken). Measured hook duration is pending a hook-only TTS endpoint and is reported as pending, never invented.

Parameterssegmentsrequired

upload_media

Upload a local image for a video segment and get a mediaId to use in a segment's media field (generate_persona_video). The file is stored as a temporary engine asset (1 hour TTL, same flow as debug uploads): upload shortly before generating, since a scheduled slot far in the future may outlive the asset and the segment then renders without it. JPG/JPEG, PNG, or WebP only, max 8MB (the engine photo limit). The real content type is validated, not the extension. Returns only the mediaId — never a local path or asset URL.

Parameterspathrequired

check_media

Mechanically check one public image URL for a segment, without an LLM and without judging relevance (the agent, who sees the image, decides relevance). Returns exactly one code: ok, blocked_403 (the host blocked the SSRF-safe download — retry with upload_media bytes fetched on your own machine), not_image, too_small, too_large, timeout, or face_detected. No fake User-Agent or Referer is sent to bypass hotlink blocks, and private addresses are never fetched. The URL never appears in the output.

Parametersurlrequired

get_video_status

Check the generation status and fetch final video URLs for a taskId.

ParameterstaskIdrequired

get_video_task_progress

Poll one video task for its machine-readable progress: returns {task_id, state, progress, stage, error}. error is the engine failure reason when state is -1 (failed), null otherwise. Use for the video after generate_persona_video; prefer this over get_video_status when only progress matters.

ParameterstaskIdrequired

list_persona_images

List the image library of a persona (up to 10 tagged images). Each entry has id, tag, description, and is_primary. Use the ids with generate_persona_video options.imageId to force a specific image for the video.

ParameterspersonaIdrequired

add_persona_image

Add an image to a persona image library from a local file path (JPG/JPEG, PNG, or WebP, max 10MB). Optional tag and description drive the deterministic per-video image selection. The server rejects full libraries (10 max).

ParameterstagpathrequiredisPrimarypersonaIdrequireddescription

update_persona_image

Update a persona library image tag, description, or primary flag.

ParametersidrequiredtagisPrimarydescription

remove_persona_image

Remove an image from a persona image library.

Parametersidrequired

publish_video_direct

Publish a ready-made video file directly to social accounts — no generation, no schedule. Reads a local video (.mp4 or .mov, max 2GB) and posts it immediately via POST /api/upload-content. YouTube requires title, description, tags, and privacyStatus; Instagram/Bluesky/LinkedIn require caption.

ParameterstagstitlecaptionproviderrequiredvideoPathrequiredaccountIdsrequireddescriptionprivacyStatus

Version history

1
  1. v1.32.0LatestOct 30, 2026

Score

90% of the applicable checks met

How complete this listing is, computed from the registry entry, the endpoint and public sources. Not a rating of the server and not a statement that it is safe or working today: reachability is a dated check, and no installs or reviews go into the score.

  • Reached in the last 30 days25 pts
  • Auth declared10 pts

    Declare the credential headers in the registry entry, or keep the endpoint answering the probe so its auth can be classified.

  • Tools listedRequired15 pts
  • DescriptionRequired15 pts
  • Logo10 pts
  • Documentation15 pts
  • License10 pts