Brand-Ops MCP Server
Identity provider used to authenticate MCP and REST callers: a bearer token can be a suite JWT from the configured Keycloak realm (default uchub) or a trusted Brigade issuer. Those suite credentials can be exchanged for Brand-Ops personal access tokens (brand_pat_ prefix), and the same viewer/member/admin/owner role model as the REST API governs tool access.
One of the selectable image-generation backends for asset creation: tools like generate_asset can route an image request to OpenAI, alongside Gemini, Recraft, FLUX/ComfyUI, OpenRouter, or a LiteLLM gateway. The chosen backend is only called for real generations (dry-run/prompt-preview paths return the composed prompt without spending on a model).
Persistence and tenancy layer behind the brand kits and workspaces. Postgres row level security is forced on the tenancy tables so each credential sees only its workspace, brands are stored as files under a workspace-scoped path, and a DATABASE_URL to Postgres is required for credential/workspace resolution when the MCP server runs over stdio.
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., "@Brand-Ops MCP Serverdraft an on-brand social card for Acme and flag any compliance issues"
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.
What is Brand-Ops?
Brand-Ops is the system of record for a brand and the studio that produces its assets. It stores each brand's palette, type, voice, and rules as data, then lets a person or an agent generate assets against that record and check an image or a piece of copy for compliance. One engine sits behind a command line, a REST API, an MCP server, and a web app. It is self-hosted and multi-tenant, and it is part of Unicorn Commander, which shares sign-in and can share inference and entitlements across the suite. Agents draft, route, and critique. An asset marked needs_review does not count toward asset coverage. A generation that calls a model does not run until something asks for it.
Related MCP server: wrmmax-criativo-mcp
Highlights
The brand is data, not a slide deck.
brand.yamlholds colors, fonts, voice, forbidden phrases, and the mark. The CLI, API, and MCP tools load that file; the GUI reads the API's projection of it.Look before you spend.
build_promptreturns the composed prompt;brand-ops generate --dry-runprints it. No image backend is called.Text cards are drawn, not guessed. Share images and social cards draw the supplied text with bundled fonts and the brand's colors; the launcher tile has no text. A decodable raster mark is composited when available, and the same inputs produce the same pixels.
Compliance records a verdict and a review state. Copy is failed for a brand's forbidden phrases or an em-dash. Colors are compared with the palette. Failures are stamped
needs_review; owners can change that state through the API or MCP.Agents do the drafting. Iris (the
director) talks in the Studio and routes the ask. Specialists research, take a brand in, pack assets, draw avatars, critique, tag, and refine. Every brand has a steward in the team view. The default steward uses the brand mark as its avatar; created agents can have generated avatars and a bound voice.Tenants are isolated twice. Postgres row level security is forced on the tenancy tables, and brand files live under
brand-kits/<workspace_id>/<slug>/when a workspace is in scope.Several ways to make a picture. Image calls can go to Gemini, OpenAI, Recraft, a local FLUX/ComfyUI server, OpenRouter, or a LiteLLM gateway. Five locked template definitions are exposed by REST and MCP without calling a model; the HTML canvas renderer is in
web/src/pages/templates/render.ts.A link can unfurl on brand.
share-metaprints the Open Graph and Twitter tags for a brand's own site, and the same kit is onGET /api/brands/{slug}/share-preview.
Feature tour
Portfolio
The front door lists every brand you can see, with coverage of the asset types and the agents on watch. Opening a brand themes the workspace to that brand's palette and sets its display and body font stacks. Inter and Space Grotesk are bundled; other families use locally available fonts or fallbacks.
Brand HQ
A brand's home screen is its mark, its grade, and its color tokens, drawn from the brand record rather than a separate theme file.
Studio
The Studio is where a steward or Iris takes a plain-language ask: build a kit, make an OG image, or check assets against the rules. A pack request runs the asset_pack agent. A single asset can be previewed as a prompt before anything is generated.
Brand book
Guidelines are rendered from the brand record: identity, color tokens, typography, voice, asset coverage, and a link-preview kit you can copy.
Launcher tile
The launcher_tile asset type (512×512) is intended for an Ops-Center launcher icon. Use brand-ops card --asset launcher_tile --brand <slug> to compose it from the palette and an available raster mark without a model. Registration notes are in docs/ops-center-registration.md; its SQL examples require verification against the target Ops-Center schema.
The same GUI also has a library (/library and /b/:slug/assets), a mark page (/b/:slug/mark), a grade page (/standards and /b/:slug/grade), a crew page for the per-brand cast (/crew), new-brand intake (/brands/new), and settings. /workflows and /templates redirect into the Studio.
Built for agents
Brand-Ops exposes brand and asset workflows as MCP tools, so an agent can list brands, compose a prompt, generate or draw an asset, check compliance, and stand up a steward without clicking through the app.
Transports. Stdio is python -m brand_ops.mcp_server. It refuses to start unless BRAND_OPS_MCP_TOKEN is set. Calls enforce the resolved workspace and role; stdio authentication is cached for up to 60 seconds before revalidation. Streamable HTTP is mounted at /mcp only when MCP_HTTP_ENABLED is true. HTTP MCP always requires a bearer. Turning AUTH_REQUIRED off for local GUI work does not open /mcp. Discovery, when HTTP is on, is GET /.well-known/mcps.json.
Auth. A bearer is either a suite JWT (the configured Keycloak realm, default uchub, or a configured trusted Brigade issuer) or a Brand-Ops personal access token. Tokens use the prefix brand_pat_ and are minted at POST /api/tokens by a member, admin, or owner using a non-delegated suite JWT. PATs and delegated agent credentials cannot mint PATs. The anonymous local principal cannot mint one. Tools are allowed or refused with the same roles as the REST API: viewer, member, admin, owner.
Surface. 36 tools, 3 resources, 4 resource templates, and 3 prompts. The resources are brand-ops://brands, brand-ops://asset-types, and brand-ops://templates; resource templates expose per-brand guidelines, assets, tokens, and agents. The prompts are launch_full_brand_kit, audit_brand_compliance, and stand_up_steward_agent.
Tools you will actually call:
Tool | What it does |
| Read the brands this credential can see, and one brand's identity |
| Compose an image prompt and return it. No backend call |
| Generate one asset with the selected backend; hosted providers may charge |
| Draw an OG image, social card, or launcher tile. No model |
| Fill the asset types a brand is still missing |
| Check copy or a file, or roll up a brand |
| Talk to Iris and get a routed intent |
| The Open Graph and Twitter tags for a brand's site |
| Draft a brand, create one, or copy one |
| Move an asset through review; requires the workspace owner role |
Eight agent implementations are listed in the Brigade-format registry: director (Iris), brand_research, brand_intake, asset_pack, avatar, brand_critic, tagger, and refine. A ninth agent class, exposed as generation_planner, turns free text into a generation request and is not in that registry. There is no separate A2A endpoint. The registry can build a Brigade-format manifest, and brand_research can call Brigade's research API. Research requires BRIGADE_API_KEY; BRIGADE_BASE_URL defaults to https://brigade.unicorncommander.ai and can be overridden. Agent chat falls back in persona when the LLM gateway has no key.
Stdio client, launched from the repo root after the source install below (credential and workspace resolution need Postgres):
{
"mcpServers": {
"brand-ops": {
"command": "uv",
"args": ["run", "python", "-m", "brand_ops.mcp_server"],
"env": {
"BRAND_OPS_MCP_TOKEN": "<token>",
"DATABASE_URL": "postgresql+asyncpg://<user>:<password>@localhost:5432/brand_ops",
"BRAND_OPS_MCP_WORKSPACE_ID": "<workspace-uuid>"
}
}
}
}HTTP client, after the API is up with MCP_HTTP_ENABLED=true:
{
"mcpServers": {
"brand-ops": {
"url": "http://127.0.0.1:8000/mcp",
"headers": {
"Authorization": "Bearer <token>"
}
}
}
}BRAND_OPS_MCP_WORKSPACE_ID only selects a workspace the token's owner already belongs to.
Quick start
You can list brands, dry-run a prompt, draw a card, and open the GUI with no provider key and no suite login. Model-backed image generation needs a configured cloud backend or a reachable FLUX/ComfyUI server. With no usable backend, POST /api/generate returns HTTP 422. Agent chat answers 200 with an in-persona fallback when no LLM key is configured. /health is the liveness probe. /readyz stays not-ready until Postgres answers and a backend in FALLBACK_IMAGE_BACKENDS passes its health check; the default list is litellm,openrouter, and this path configures neither.
The CLI imports Pillow. Pillow is the backends extra, so install that or brand-ops will not start.
Docker
The API image serves the REST API on port 8000. It does not include the web GUI. AUTH_REQUIRED defaults to true, and a container that leaves it unset will refuse to boot unless DATABASE_URL and BRAND_OPS_BOOTSTRAP_WORKSPACE_ID are set. For a local look, turn that check off explicitly and bind the port to loopback. Refresh the lockfile with uv before building: its package metadata trails pyproject.toml at this revision.
uv lock
docker build -t brand-ops .
docker run --rm -p 127.0.0.1:8000:8000 -e AUTH_REQUIRED=false brand-opsThen open http://localhost:8000/docs and http://localhost:8000/api/brands. The image copies the curated kits in and seeds them on boot, copy-if-missing, into /app/brand-kits for this local run. Mount a volume there to keep generated assets across container removal.
The GUI image is web/Dockerfile.prod. The suite stack connects the GUI, API, Postgres, and an oauth2-proxy to an existing Keycloak realm:
docker compose --env-file deploy/.env -f deploy/docker-compose.unicorncommander.yml up -d --buildRun that from the repo root. It expects the suite's external Docker networks, Traefik, a root .env, deploy/.env (database passwords), and deploy/.env.oauth2 (the oauth2-proxy). Those env files are not in git. The public host in that compose file is brand-ops.unicorncommander.ai, and sign-in is the shared Keycloak realm. deploy/docker-compose.prod.yml is an alternative suite profile with different proxy and external-service settings. deploy/docker-compose.yml is the API alone and also expects those external networks. None of the compose files is a single-machine docker compose up with no surrounding suite.
With auth left on, the process also requires DATABASE_URL and BRAND_OPS_BOOTSTRAP_WORKSPACE_ID. Migrations run as the database owner (alembic upgrade head). The runtime role is not allowed to bypass row level security. See docs/deploy.md and docs/DEPLOY-TENANCY.md.
Run from source
Requires Python 3.12 or newer, uv, and Node for the GUI. The production web image builds on Node 20.18.0.
uv sync --extra backends --extra dev
export AUTH_REQUIRED=false
uv run brand-ops list-brands
uv run brand-ops generate --brand magic-unicorn --asset og_image \
--topic "investor update" --dry-run
uv run uvicorn brand_ops.api.app:app --reload --host 127.0.0.1 --port 8000In a second terminal:
npm --prefix web install
npm --prefix web run devThe GUI is at http://localhost:5173. It calls relative /api paths, and the Vite dev server proxies those to http://localhost:8000 (web/vite.config.ts).
Draw a card without a model. The compositor uses a decodable raster mark when available; SVG-only or missing marks fall back to another raster logo or a card without a mark:
uv run brand-ops card --brand brand-ops --asset og_image \
--message "Every brand, under command." --out /tmp/og.png
uv run brand-ops card --brand magic-unicorn --asset social_card \
--message "Hello from the studio."og_image is 1200×630. social_card is 1080×1080. Omit --out and the social card is saved into that brand's catalog. The web app ships a default card at web/public/og.png and the tags in web/index.html. The absolute origin in those tags is BRAND_OPS_SITE_URL at build time, otherwise https://brand-ops.unicorncommander.ai.
Print the tags for a brand's own site. The canonical URL comes from --url or brand.website; --image-url selects the hosted image, otherwise it defaults to <site>/og.png. With no site configured, the tags use a marked example domain:
uv run brand-ops share-meta --brand magic-unicorn --url https://magicunicorn.techHost the image at that URL and paste the tags into the brand's own site. The brand book's Link preview section has a site-URL field and a copy button; generating tags does not publish them to that site.
Bring a brand's known on-disk files into the managed catalog. Source locations come from src/brand_ops/ingest.py:IMPORT_SOURCES; missing paths are skipped, so external source trees may need to be supplied first. This writes under brand-kits/ and is safe to repeat:
uv run brand-ops ingest --brand majiks
uv run brand-ops ingestingest copies into brand-kits/<slug>/imported/<asset_type>/ and writes a catalog sidecar next to each file. Generated and imported bytes are gitignored. The Library and API serve cataloged assets; use Library upload for files outside the ingest mappings.
Other CLI commands: backfill-compliance stamps each catalog sidecar with the checker's lifecycle status.
Configuration
Copy .env.example to .env. Never commit .env. For source runs, export the configured values into the process environment; the Python settings loader does not load .env itself.
Variable | Meaning |
| Key for the Gemini image backend |
| Key for the OpenAI image backend |
| Key for the Recraft backend, used when you want a native SVG path |
| Base URL of a ComfyUI server for the local FLUX backend |
| Unicorn Brigade base URL |
| Key sent to Brigade |
| Reserved S3 endpoint setting; no asset mirror is implemented |
| Reserved S3 access-key setting; currently unused |
| Reserved S3 secret-key setting; currently unused |
| Reserved S3 region setting; currently unused |
| Reserved S3 bucket setting; currently unused |
| Realm URL used to verify suite SSO tokens |
| OAuth client id for this app |
| Client-secret setting read into configuration; no runtime consumer in this package |
| SQLAlchemy URL for the tenancy database ( |
Additional settings are read directly from the environment:
Variable | Meaning |
| Defaults to true. Every |
| Workspace id the seeded kits are bound to when auth is on |
| Mount |
| Bearer the stdio MCP server requires before it will start |
| OpenAI-compatible base URL for agent chat |
| Key for that chat gateway. If empty, |
| Gateway root. When set, image calls can go there instead of a provider key in this process |
| Virtual key for that gateway |
| Gateway image model; chat-style image models carry reference images |
| Direct OpenRouter image key and fallback key for agent chat |
| Image model used by the direct OpenRouter adapter |
| Comma-separated fallback order and readiness-check list; defaults to |
| Advertised HTTP MCP URL; also adds its host to the MCP host allow-list |
| Off unless set. When on, an authenticated user with no workspace may provision one |
| Absolute origin written into Open Graph tags at web build time |
| Soft per-workspace check of recorded image spend over the last 30 days, in US cents. Unset or |
| Spend-route limit. Default |
| Optional error reporting. The SDK is the |
Further knobs for entitlements, CORS, the admin allow-list, and Orator TTS are read in src/brand_ops/config.py and the API modules; retention is handled by src/brand_ops/retention_sweep.py. Entitlement enforcement is off unless BRAND_OPS_ENTITLEMENT_ENFORCED=true (or another accepted true value). The feature key it checks defaults to brand_ops_access.
Self-serve signup. Off by default. An operator otherwise creates a workspace and grants members. With BRAND_OPS_SELF_SERVE_SIGNUP=true, an authenticated suite user who has no workspace gets a setup screen, and POST /api/onboarding/provision creates an owner and a default workspace. It is idempotent and audited. Brand-Ops does not bill. Hosted-generation access and workspace image budgets use the entitlement check and BRAND_OPS_IMAGE_CAP_CENTS; agent governance also supports weekly budgets. Letting a brand-new person register a uchub account is a Keycloak realm setting, not a Brand-Ops flag. See docs/DEPLOY-TENANCY.md.
Architecture
flowchart LR
subgraph clients [Clients]
Web[Web GUI]
CLI[CLI]
MCPClient[MCP client]
end
Web --> API[FastAPI]
CLI --> Lib[brand_ops library]
MCPClient -->|stdio| MCP[MCP server]
MCPClient -->|HTTP when enabled| API
API --> Lib
MCP --> Lib
Lib --> Kits[brand-kits on disk]
API --> DB[(Postgres with RLS)]
Lib --> Img[Image backends]
API --> KC[Keycloak]
Lib --> Brig[Unicorn Brigade]
API --> OC[Ops-Center entitlements]Piece | Where it lives |
Web GUI |
|
REST API |
|
CLI |
|
MCP |
|
Engine | Prompt builder, deterministic cards, compliance, catalog, ingest |
Brand files |
|
Database | Postgres 16 in the compose files. Eight Alembic revisions under |
Image backends |
|
Agents |
|
Optional edges | Orator for voice preview, Redis for the rate limiter, Sentry |
On a fresh checkout the CLI lists 29 brands under brand-kits/. _template/ is the authoring starter and is not one of them. Asset-type target sizes are logo (2048²), app_icon (1024²), launcher_tile (512²), og_image (1200×630), podcast_cover (3000²), social_card (1080²), and avatar (1024²). Named generation packs are core, social, logo, podcast, and launch. Locked templates are og_hero, social_square, social_story, quote_card, and feature_announcement.
What the server enforces
Auth fails closed. A missing
AUTH_REQUIREDmeans on, not off.By default, generation, planning, voice-preview, intake, agent-run, and signup REST POSTs are rate limited;
/mcpis outside this limiter. The default is 30 requests per minute per caller bucket, per process (BRAND_OPS_RATELIMIT)./health,/readyz,/metrics, and reads are not limited. The store is in-memory unlessBRAND_OPS_RATELIMIT_REDIS_URLis set (ratelimit-redisextra).Activity and admin audit logs are hash-chained.
GET /api/admin/workspaces/{workspace_id}/audit/verifychecks that workspace's activity chain.Uploaded rasters are opened and checked against the claimed format. Intake and the LiteLLM/OpenRouter result fetchers validate initial URLs against public HTTP/HTTPS rules. Their redirect checks run after fetching; the Recraft result downloader does not use this guard.
Admin routes support workspace export (
GET /api/admin/workspaces/{workspace_id}/export) and deletion, gated by the operator allow-list and admin tier. Deleting a workspace requires a matching confirmation./readyzreturns 503 when Postgres is unset or down, or when no backend inFALLBACK_IMAGE_BACKENDSpasses its health check, so a load balancer can stop sending traffic. Shutdown closes the database pool.Prometheus metrics are on
/metricswhenprometheus-fastapi-instrumentatorimports, which it does as a core dependency.
Layout
brand-kits/<slug>/brand.yaml brand definition
brand-kits/<slug>/refs/ reference images
brand-kits/<slug>/imported/ ingested assets, not in git
brand-kits/<slug>/generated/ model output, not in git
brand-kits/<slug>/agents/<agent>/ avatar and persona
src/brand_ops/ library, CLI, API, MCP, agents, database
web/ GUI
deploy/ compose files and the Postgres init scriptThe single path resolver is brand_loader.current_kits_dir(). Set BRAND_OPS_BOOTSTRAP_WORKSPACE_ID to the destination workspace UUID before copying an existing flat tree into per-workspace directories. Inspect the copied files before --cleanup: that command only checks for a destination brand.yaml before deleting a flat brand directory.
python -m brand_ops.migrate_storage --dry-run
python -m brand_ops.migrate_storage
python -m brand_ops.migrate_storage --cleanupConventions. Use uv, not pip. Brand-facing copy does not use em-dashes or marketing filler. The copy linter reports prompt warnings; it does not block generation. Do not commit generated assets, imported bytes, or secrets.
Part of Unicorn Commander
App | Connection in this repository |
Entitlement client and launcher registration notes | |
Research client and agent manifest definitions | |
Brand kit: | |
Brand kit: | |
Brand kit: | |
Brand kit: | |
Brand kit: | |
Brand kit: | |
Brand kit: | |
Brand kit: | |
Suite link; no dedicated adapter in this tree | |
Brand kit: | |
Brand kits, marks, voices and rules | |
Suite link; no dedicated adapter in this tree | |
Suite link; no dedicated adapter in this tree | |
Suite link; no dedicated adapter in this tree |
Brand-Ops verifies suite JWTs against the configured Keycloak realm, defines agent manifests for Unicorn Brigade, can call Brigade's research API, and resolves the brand_ops_access entitlement from Ops-Center using an eligible Keycloak bearer when BRAND_OPS_ENTITLEMENT_URL is set. Image and agent calls can be sent to a LiteLLM gateway instead of a provider key stored here. For chat-style image models, the image adapter sends brand references through /v1/chat/completions and includes the active workspace as user. Image runs record per-workspace spend on a best-effort basis; agent chat also records usage when the gateway returns it. The launcher_tile asset can be used as the app icon when registering it in Ops-Center.
Development
uv sync --extra backends --extra dev
uv run pytest
uv run ruff check src tests
npm --prefix web test
npm --prefix web run typecheckuv run pytest --collect-only -q collects 1011 tests. Ruff is configured in pyproject.toml (line length 100, target Python 3.12). The web test script is Vitest (npm --prefix web test). npm --prefix web run build produces web/dist.
The package manifest declares 0.32.0; /health and the web manifest currently report 0.29.2. The committed lockfile still records package 0.30.0, so the source setup above lets uv refresh it. A Forgejo Actions workflow in .forgejo/workflows/ci.yml defines lint, Python tests, web checks, and dependency scans; it requires a configured runner.
With a tenancy database configured, apply migrations as the owner role:
uv run alembic upgrade headBackup and restore scripts are scripts/backup-brand-ops.sh and scripts/restore-brand-ops.sh. The notes for them are docs/BACKUP.md. They cover Postgres and the brand-kits volume; off-host copies require restic or S3 configuration. Back up deployment env files separately.
See CONTRIBUTING.md for the local loop and what not to commit.
Security
The reporting policy directs private vulnerability reports to this repository's GitHub Security page. This tree does not publish a security email address. Please do not file a public issue that includes exploit detail. See SECURITY.md.
Do not run with AUTH_REQUIRED=false on a host other people can reach. That mode permits unauthenticated access to the flat brand-kits/<slug>/ tree, including generation and asset writes.
Licence
Brand-Ops is licensed under the GNU Affero General Public License v3.0 or later. See LICENSE. If you run a modified version as a network service, the AGPL requires you to offer the corresponding source to its users.
This server cannot be deployed
Maintenance
Related MCP Connectors
Brand-safe MCP for AI agents to create editable, on-brand graphics and automate variants.
Generate on-brand images from your AI agent: design, edit, and render templates over MCP.
Generate contextual prompts and reusable agent skills, evaluate prompts with the 16-dimension Prompt Score, and manage saved work in PromptDrive. Twelve MCP tools also provide authorized access to private Memory for source-grounded answers. Connect over Streamable HTTP using OAuth 2.1 and PKCE. Generation consumes account quota and automatically saves successful results; Memory access follows account permissions and plan limits.
Generate marketing images, videos and audios for campaigns, product content, and brand assets.
Related MCP Servers
- FlicenseNot gradedqualityBmaintenanceEnables to plan, write, illustrate, and voice a full marketing campaign through an MCP pipeline that generates copy, moodboards, voiceovers, and images with automated guardrails and budget controls.-
- FlicenseNot gradedqualityBmaintenanceEnables an AI assistant to generate and edit brand-compliant marketing images through MCP tools, including creating visuals from prompts, modifying reference photos, and refining results based on prior output.-
- AlicenseNot gradedqualityBmaintenanceEnables image and video generation and editing through MCP tools, including image editing, upscaling, background removal, video generation, image-to-video, and video extension. Video generation runs asynchronously with job status and result retrieval.MIT

Popcraft MCPofficial
AlicenseNot gradedqualityBmaintenanceEnables AI agents and MCP clients to generate and edit video, images, 3D, music, and sound effects, upload or reuse media, preview credit costs, and follow production workflows through a single hosted connector.MIT