wovain
Provides tools for building a Meta Advantage+ sales campaign as a Meta draft, creating five automated rules in Meta, and launching the campaign (Launch requires a payment method on the ad account).
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@wovaindesign a tee for a coffee lover, publish it, make two ads, and build a Meta campaign"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
wovain
An MCP server for print-on-demand sellers, hosted on Firebase App Hosting, that any agent (ChatGPT, Claude, …) can connect to. In one chat it designs a product, publishes it to a Fourthwall shop, makes two short video ads for it, and builds one Meta Advantage+ sales campaign with five automated rules.
Built for Devpost's Build With AI: Basics with the Devpost Learn skill pack. The planning documents are in docs/: scope, PRD, spec, build checklist and an app map of the code.
What works
Step | What you see in the chat | Status |
Settings | Settings widget: budget, rule multiples, video model and audio | Working |
Design | "design a tee for: …" → design widget with mockups, price, profit and margin; Approve publishes it on Fourthwall | Working |
Ads | Ad widget with 2 to 4 ad concepts per product (hook, audience, why now), Feed and Stories visuals (fal images, mockup, optional video), text variants for Advantage+, a carousel across the set and the breakeven line; Approve, Skip or feedback per concept | Working |
Campaign | Campaign widget: one Advantage+ sales campaign built as a Meta draft, the five rules created in Meta, Launch to publish | Working (Launch needs a payment method on the ad account) |
Optimization check-in, mock mode | — | Planned, not built (see the checklist's Revisions) |
Related MCP server: autowhisper-mcp
Requirements
An agent that speaks MCP over streamable HTTP with OAuth, such as ChatGPT (developer mode) or Claude
Node.js 24 and pnpm 12.3.4 (
corepack enablepicks it up frompackageManager)Java 21, for the Firestore and Storage emulators that
pnpm devandpnpm test:integrationrun onIn the same agent, these MCP servers connected and signed in: Fourthwall, Meta Ads and fal
A Fourthwall shop, a Meta ad account with a Facebook Page and a pixel, and fal credit (about $0.18 per ad video at fal's list prices: one 5-second H3 Max Turbo clip, its audio and three crops)
Setup
git clone https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43085639/showcase.git wovain
cd wovain
pnpm install
cp apps/server/.env.example apps/server/.envFill in apps/server/.env (empty values count as unset; in production they come from apphosting.yaml, with secrets set by pulumi config set --secret <name> in infra/ (encrypted with a KMS key, applied by pnpm infra:up)):
Key | Needed for | Where to get it |
| Hosting finished videos, live video prices | fal dashboard → API keys |
| Optional: merchants connecting Meta to wovain (Meta's Ads MCP has no dynamic registration, so wovain's own Meta app is the OAuth client). Without them, merchants use their host's Meta Ads connector and rules aren't created | Your Business-type Meta app (Ads MCP use case) → App settings → Basic. See Meta app |
| Optional: the design check ( | Nebius Token Factory → API keys |
| Optional: Qloo's Taste API, for the taste graph under | The hackathon's event key (it works only on |
| Optional: trend research, the niche brief, the competitor scan and the trademark screen ( | Tavily dashboard → API keys |
| Sign-in: the AuthKit domain that issues | WorkOS dashboard (e.g. |
| Reads each caller's email for the allowlist | WorkOS dashboard → API keys |
| Who may use | The email you sign in to AuthKit with |
| Optional: overrides the site URL, e.g. a tunnel to your local server | Your tunnel's URL |
| Optional: send traces to GitLab Observability (unset sends nothing; production sends to the group's endpoint, set in | GitLab group → Observe → Setup |
| Optional: the auth header for that endpoint, as | Group → Observe → API Keys |
Storage needs no keys: locally pnpm dev starts the Firestore and Storage emulators, and on App Hosting the server finds its Firebase project and bucket on its own.
Connect an agent
Add <App Hosting URL>/mcp (https://app--wovain.us-central1.hosted.app/mcp) as a remote MCP server (streamable HTTP) in your agent, or https://<tunnel>/mcp for a local server. It carries no credentials: the first time you use wovain, the agent sends you to sign in with Google and approve access. Then try "show my wovain settings".
ChatGPT's connector icon must be under 10 KB; use apps/server/public/icon-200.png (also served at <site>/icon-200.png).
The server tells the agent the flow in its MCP instructions, and serves a playbook per step (design, ads, campaign) through the get_playbook tool and as MCP prompts.
Nebius and Nemotron
wovain makes its own model calls through Genkit, with the OpenAI-compatible plugin (@genkit-ai/compat-oai) pointed at Nebius Token Factory (https://api.tokenfactory.nebius.com/v1/). The model is NVIDIA Nemotron 3 Super (nvidia/nemotron-3-super-120b-a12b), set once in apps/server/src/lib/ai.ts.
Call | When | What it does |
| The design playbook calls it before every design widget | Scores the design brief (idea, blank, placement, colors, artwork size and transparency, listing, coach line) against wovain's print and listing rules. Returns a verdict, one check per rule, up to 5 fixes and a coach line, checked by a zod schema. Code decides print size, transparency, title length and color count itself, and those results override the model. |
To run it:
Put a Token Factory key in
apps/server/.envasNEBIUS_API_KEYand runpnpm dev. Without the key,judge_designanswersconfigured: falseand the agent skips the check.pnpm test:livemakes one real call with the demo brief and checks the reply against the schema (a fraction of a cent). It's skipped without the key.pnpm testandpnpm test:integrationuse Genkit's mock model and never call Nebius.In production, set the secret (
pulumi config set --secret nebius-api-keyininfra/) and listNEBIUS_API_KEYinapphosting.yaml.
Tavily
How wovain uses Tavily, through the official @tavily/core SDK at runtime (no hand-rolled HTTP). Every call asks for include_usage, and research_trends returns what the run cost as credits.
Endpoint | Where | What it does |
|
| This week's news ( |
|
| Etsy, Redbubble and Fourthwall listings ( |
|
| One call with a |
|
| The merchant's own Fourthwall shop ( |
|
| A cited niche brief (model |
|
| Up to 5 pages of a competitor's site for the ads flow's angles and offers |
|
| The phrase plus "trademark" before publishing |
No model ranks ideas. Code scores each theme with fixed weights (SCORE_WEIGHTS in apps/server/src/lib/score.ts): demand (Qloo affinity, plus active Meta ads when the agent passes adCounts), momentum (news, discounted when Qloo's velocity doesn't confirm it), Tavily relevance, recency, openness (few listings and best sellers), margin (competitor prices against the merchant's own price floor) and IP safety (no Qloo brand, artist or title names). The host agent (ChatGPT, through the design playbook) phrases the ideas and why from that evidence.
A full run with Qloo costs about 35 to 50 credits: 6 searches, 2 for the extract, up to 4 for the map, and the niche brief, which Tavily prices at 4 to 110 credits (wovain reserves 25 and asks for a short report). research.maxCredits (default 50) caps it: the searches always run, then the brief, the extract and the map run in that order of priority while their worst case still fits. The brief's real cost is known only when it finishes, so get_niche_brief reports it. Without Qloo the seed is the one theme. A re-run with the same seed within 15 minutes only re-scores and costs nothing. Nothing about queries or results is logged, every URL is http or https, and every result says its text is untrusted.
The weekly market scout (apps/ops/src/weekly.ts) uses the same key from CI for 3 news searches about competing print-on-demand AI tools, and files one status::intent issue with what it found and the funnel's biggest drop-off.
Put a key in apps/server/.env as TAVILY_API_KEY to try them locally. pnpm test:live runs one real research capped at 10 credits (about 3) and is skipped without the key. In production the key is the tavily-api-key stack secret, listed in apphosting.yaml.
Qloo
Taste × moment: Qloo says who loves the seed and what else they love; Tavily says why now. With QLOO_API_KEY, research_trends looks the seed up on Qloo (/search), takes the cross-domain taste tags its fans share (/v2/insights) and the seed's velocity (/v2/trending), then researches each of the top 3 tags on Tavily (above). Their affinity and the seed's velocity go into the score; the same seed again within 15 minutes (say, with adCounts from the agent's Ad Library search) is only re-scored. suggest_audience turns a picked idea's tags into age bands, gender and top places (urn:demographics, urn:heatmap) for the campaign.
wovain calls Qloo's REST API itself: the hackathon kit's @qloo/qloo-harness is a CLI and stdio MCP server that doesn't export its client. Hackathon keys work only on https://hackathon.api.qloo.com, with the X-Api-Key header. Put the key in apps/server/.env as QLOO_API_KEY; pnpm test:live then makes 5 real Qloo calls. In production, set the qloo-api-key stack secret and add QLOO_API_KEY to apphosting.yaml once it has a value.
Develop
Command | What it does |
| Starts the Firestore and Storage emulators and serves |
| Builds the widgets; Node runs the server's TypeScript as is |
| Runs the server the way App Hosting does ( |
| Rebuilds widgets on save; redraw a widget in chat to see the change |
| Every widget state, light and dark, at ChatGPT widths, without ChatGPT |
| Lint and format (Ultracite: oxlint + oxfmt) |
| TypeScript, strictest settings |
| Server unit tests (Vitest): pure logic, no services |
| Store and MCP tool tests on the Firestore and Storage emulators (needs Java 21) |
| The whole journey as an agent drives it, on the emulators with recorded Fourthwall, fal, Meta, Tavily, Qloo and Nebius replies; writes a proof report with each step's redacted input and output, timing and widget screenshot to |
| After |
| Opt-in: one real Nemotron design check on Nebius, one real Tavily research (about 3 credits) and one Qloo taste and audience lookup; each is skipped without its key ( |
| Widget stories as tests (Playwright) |
| Unused files, exports and dependencies |
| Architecture rules (dependency-cruiser): layers, cycles, orphans |
| Copy-paste detection (jscpd); any clone fails |
Git hooks (Lefthook) run fix and typecheck on commit, commitlint on the message, and tests, knip, arch, dupes and story tests on push.
The code lives on GitLab, with GitHub as a read-only mirror that CI keeps on main; nothing builds from it. GitLab CI/CD (.gitlab-ci.yml) runs, in order:
Stage | What runs |
verify | Build, lint, typecheck, knip, arch, dupes, tests, card stories (in parallel) |
test | GitLab SAST, Secret Detection and Dependency Scanning |
deploy |
|
monitor | A smoke test of the live OAuth chain; hourly on the |
release | semantic-release, after the smoke test passes; then |
How work moves
Each idea or bug is a GitLab issue (template Intent, Agent task or Bug) on the Workflow board:
status::intent: proposed (templateIntent). You accept it by moving it to todo.status::todo: accepted, ready to pick up. The agent turns it intointent.md,spec.mdandplan.mdindocs/changes/<issue>-<slug>/, moving the issue throughstatus::specandstatus::plan, and comments a summary with links. For a risky change (auth, infra, CI config, money-spending paths, stored record shapes, apriority::1feature) it waits atstatus::planuntil you approve the plan in a comment.status::doing: an agent took it. In Claude Code,/issue <n>works it in its own worktree on your Claude subscription; run two or three at once. Assigning or mentioning Duo's Developer flow works it on GitLab instead.status::review: the MR is open. Duo's Code Review flow reviews it when requested as reviewer.status::test: your turn. Try it, then comment what to change or merge.verify.mdin the change folder says how it was proven.
Production errors skip the first two steps. The hourly self-healing loop (docs/loop.md, apps/ops) reads Cloud Logging, Cloud Monitoring and GitLab Observability, files each new error as a scout issue built only from server-controlled fields (route template, allowlisted error class, status) and assigns the known ones to Duo's Developer flow, takes Duo's merge request through Duo Code Review to GitLab's auto-merge when it passes the guardrails, and after the deploy checks that the error rate dropped: it closes the issue with the numbers, or reopens it. Its daily comment on the digest issue says what it found, fixed and handed back. On Mondays the wovain weekly schedule adds the data analysis there, and starts the Duo scout flow through a child pipeline so it posts its own check: how many merchants reached each funnel step (connect, idea, set approved, ad approved, campaign created, autopilot run), requests, instance time, Duo credits and estimated fal, Tavily and Nebius costs. The server also counts each step as the OpenTelemetry metric wovain.funnel.events in GitLab Observability. The Duo triggers it relies on are in infra/Pulumi.prod.yaml.
Agent skills
The project's own skills (/issue, /capture, /demo) are written once, in .agents/skills/<name>/SKILL.md. Each agent finds them there:
Agent | How it reads them |
GitLab Duo | Reads |
Claude Code | Through |
.agents/skills/ also holds ten claude-ads skills (ads and ads-*, MIT, credited in NOTICE) for the optimizer. They're vendored unchanged with pnpm skills add AgriciDaniel/claude-ads --skill <names> -a universal -y --copy and pinned by hash in skills-lock.json; pnpm check runs pnpm skills:check, which fails if any vendored file changes. Update them only by running skills add again. wovain's print-on-demand rules live in its own overlay, .agents/skills/wovain-pod-ads/, which says to read the matching claude-ads skill first.
The course skills and ultracite are copied into .claude/skills/ by pnpm install and stay out of git, so only Claude Code sees them. Plugins from .claude/settings.json are Claude Code only too: a headless claude run in CI loads them only after claude plugin install <plugin>@<marketplace> --scope project.
Architecture and data
docs/architecture.md: C4-style context and container views, and how a change moves from an idea to production.
docs/data-model.md: every Firestore collection and the media bucket, who writes and reads each, and how long it lives.
docs/loop.md: the self-healing loop. docs/transcend.md: how wovain answers the GitLab Transcend prizes.
DevSecOps stages
Every stage runs on its own after a merge, with GitLab features, Duo flows or the project's own CI jobs. Built-in flows and deterministic CI code come first. The only custom agent, wovain explain, is read-only: it has no write tools.
Stage | Automated by | Where |
Plan | Issues on the Workflow board ( |
|
Create | Duo Developer flow (assign or mention, and the loop's assignments), Claude Code |
|
Verify | CI verify stage (lint, types, knip, arch, dupes, unit, integration and story tests), Duo Code Review with project rules, Duo Fix CI/CD on a red |
|
Package | App Hosting builds the container from the source CI uploads; semantic-release versions it |
|
Secure | GitLab SAST, Secret Detection and Dependency Scanning on every MR and |
|
Release | semantic-release (version, changelog, GitLab release) after the smoke test; auto-merge for the loop's own fixes |
|
Configure | Pulumi applies Google Cloud, GitLab (variables, environments, schedules, labels, board, Duo triggers), the GitHub mirror and WorkOS on every |
|
Monitor | Smoke test; GitLab Observability (traces, the funnel metric, CI pipeline telemetry); the hourly scout and verify; the weekly data analysis; SCI on every |
|
Govern | Auto-merge guardrails (allowlist, provenance, review of the head commit), CODEOWNERS, Duo review rules, Workload Identity Federation with one account per environment, a $5 budget alert |
|
Blocked for our role (see docs/transcend.md): automatic Duo review, SAST false-positive detection and Resolve SAST flows, code owner approval, error tracking, a Duo schedule for the scout flow, and enabling flows in the top-level group. "Pipelines must succeed" and "All threads must be resolved" can be ticked in the UI but not set by API.
Sustainability (SCI)
wovain emits about 8.73 gCO2e per run (one idea-to-paused-campaign run, down from 14.27 with two 15-second clips), measured with the Green Software Foundation's SCI method: ((E × I) + M) per R, with us-central1's location-based grid intensity (432 gCO2e/kWh), measured server and CI time, estimated model energy, and video energy from the GPU seconds fal reports per clip (an estimate until merchants' clips are recorded). The sci job recomputes it on every main pipeline and publishes the latest report. Method, sources and caveats: docs/sustainability.md.
Choice | Gain |
One short clip, cropped to every ratio | One 5-second H3 Max Turbo clip per ad instead of two 15-second H3 Max Reference clips: about $0.18 instead of $1.53 at list price, and 5.5 instead of 11.1 gCO2e per run at the fallback estimate |
Scale to zero on App Hosting | About 334 gCO2e a week avoided against one always-on instance (measured instance time; estimate coefficients), more than a whole run emits |
Scanners off on scheduled pipelines | About 262 CI minutes and 29 gCO2e a week avoided (measured scanner durations) |
Filtered Duo triggers | Flows only on a person's action or a red |
Deterministic code before models | The loop, verification and market scout make no model calls; the design check decides print rules in code |
Checks before credits | The shepherd hands an MR to a person before it spends a Duo review on it |
Estimates are labeled in the report. Video generation (fal) is most of the number and the least certain part.
Infrastructure as code
infra/ (Pulumi, pnpm infra:preview then pnpm infra:up, which CI runs on main) declares:
Google Cloud and Firebase: APIs, Firestore and its rules (plus TTL on pending connect links and OAuth flows), the media bucket and its Storage rules, the
wovain/connectionsKMS key that encrypts merchants' Fourthwall, fal and Meta grants, Secret Manager, App Hosting, service accounts and IAM, Workload Identity Federation for CI (no Google key is stored), and the budget (infra/index.ts).GitLab (
infra/gitlab.ts,infra/duo.ts): every CI/CD variable, theproductionandinfrastructureenvironments, the Renovate schedule, the issue labels and the Workflow board, the Duo flow triggers, the scout flow's trigger and the explain agent.The GitHub mirror (
infra/github.ts): repository settings, the deploy key CI pushes with (a generated key pair, which also becomesGITHUB_MIRROR_KEY), and a ruleset that lets only that key changemain.WorkOS AuthKit (
infra/workos.ts): the redirect URIs inwovain:workosRedirectUris(Pulumi.prod.yaml). It uses the communityosodevops/workosTerraform provider, whichpulumi installgenerates intoinfra/sdks/workos. It signs in with theworkos-api-keystack secret. Entries made by hand are adopted by their WorkOS id (import). The MCP resource indicators inwovain:workosResourceIndicatorsgo through WorkOS'sauthkit_oauth_resourcesAPI with the genericmagodo/restfulTerraform provider (generated intoinfra/sdks/restful), sinceosodevops/workoshas no resource for them. That API has no update, so a changed URI ordefaultreplaces the entry, and the default one is created first.
The tokens are stack secrets, encrypted with the KMS key (pulumi config set --secret <name> in infra/): gitlab:token (Pulumi's GitLab access, api), github:token (a fine-grained token for the mirror repository: Administration read/write), gitlab-ci-token (GITLAB_TOKEN for semantic-release and the AI Catalog sync) and renovate-token (RENOVATE_TOKEN). Each is a separate personal access token, since the Developer+ role can't create project access tokens.
Still set by hand, since no API or provider covers them (or the role can't reach them):
Where | What |
WorkOS dashboard | CIMD and dynamic client registration (Connect → Configuration), and sign-in methods. WorkOS documents no API for these, so no provider covers them. |
GitLab, Maintainer only | Enabling flows in the top-level group, a Duo schedule for a custom flow (its service account needs |
GitLab, UI only | "Pipelines must succeed" and "All threads must be resolved". Our role can tick them under Settings → Merge requests, but |
GitLab Observability (group) | The |
Google Cloud, once | The Pulumi state bucket |
Meta, Fourthwall, fal, Nebius, Qloo, Tavily | The accounts, Page, pixel, payment method and API keys (keys go in as stack secrets) |
Meta app dashboard | wovain's Meta app: its use case, Facebook Login for Business redirect URI, roles, privacy and data-deletion URLs, Business Verification and App Review. Meta's Graph API can set a few app fields (such as |
Meta app
Merchants connect Meta through wovain's own Meta app, because Meta's Ads MCP (https://mcp.facebook.com/ads) refuses dynamic client registration. The app ID is the OAuth client ID.
At developers.facebook.com/apps, create a Business-type app with the "Create & manage ads with ads MCP server" use case, in a business portfolio you control.
In Facebook Login for Business settings, add
https://app--wovain.us-central1.hosted.app/oauth/callback/metato the Valid OAuth Redirect URIs.In App settings → Basic, set the Privacy Policy URL to
https://app--wovain.us-central1.hosted.app/privacyand User data deletion tohttps://app--wovain.us-central1.hosted.app/data-deletion, plus an icon, category and contact email.Put the app ID and secret into the stack:
pulumi config set --secret meta-app-idandpulumi config set --secret meta-app-secretininfra/, then listMETA_APP_IDandMETA_APP_SECRETinapphosting.yaml.Add testers under App roles. Until App Review grants Advanced Access, only people with a role on the app can connect.
For everyone else: Business Verification, then App Review for
ads_mcp_management,ads_management,ads_read,business_management,pages_show_list,pages_manage_adsandpublic_profile, each with a screencast.
wovain asks Meta only for those scopes. create_rules uses the merchant's own token on Graph adrules_library, with appsecret_proof.
Releases
Releases come from conventional commits. semantic-release (.config/releaserc.json) picks the version, writes CHANGELOG.md, sets the version in package.json, commits that as chore(release): x [skip ci], tags it, and publishes a GitLab release. The tag v0.1.0 on the first commit is its baseline. Renovate opens dependency update merge requests every Monday from a pipeline schedule (.gitlab/renovate.json: updates wait 7 days, minor and patch are grouped).
Layout
Path | What's there |
| The Hono server on Node: |
| How App Hosting builds and runs the server, and which secrets it reads |
| The React widgets (MCP Apps) and their Storybook stories |
| The self-healing loop's CI job: scout, verify and the auto-merge shepherd (docs/loop.md) |
| Schemas, economics and rule thresholds shared by server and widgets |
| Planning documents and the app map |
| Project skills for Claude Code and GitLab Duo (see Agent skills) |
License
This server cannot be deployed
Maintenance
Related MCP Connectors
Launch Meta ads with your AI agent. Bulk-create Facebook and Instagram ads, import creatives from public Google Drive links, and launch partnership ads with authorized creators. Reuse proven ad settings, create fresh variations, and review saved builds and previews before launch. Requires an Ads Uploader account with MCP access and a connected Meta account.
Create, write and publish Printify products from chat, after a preview you approve.
Run Meta Ads and Google Ads for your Shopify store from Claude, ChatGPT or Cursor, with the store's own Shopify, Stripe or Klaviyo revenue next to ad spend. New campaigns are created paused.
Generate and manage AI UGC video ads through eleven typed MCP tools
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables automated generation and publishing of TikTok Shop Affiliate videos using 7 AI agents. Integrates research, script writing, video production, and publishing through the MCP protocol.31-
- AlicenseAqualityBmaintenanceDrives an AI CMO to add products, generate on-brand content (UGC video, posts, images), approve, and publish across 30+ platforms via natural language.225 npmMIT
- AlicenseNot gradedqualityAmaintenanceEnables AI agents to create, manage, and automate Meta (Facebook/Instagram) ads campaigns via the Marketing API, with safe defaults and AI-generated creative.1MIT
- AlicenseNot gradedqualityDmaintenanceEnables AI assistants to run a print-on-demand store by creating products on Printify, pricing from production cost, publishing to Shopify and its sales channels, and generating ad creative from mockups.1ISC