MVR
README.md
# MVR — Marketing Workflow & Visual Reference Vault
MVR is a self-hosted, local-first workspace and MCP server for keeping AI-assisted marketing work consistent with reusable brand context, visual references, evaluation rules, and human approval.
> MVR is not a video or image generation provider. It stores and governs the context around creative work. Generation can be delegated to an external provider through a future or custom provider adapter.
## What MVR provides
- Reusable creative **Elements** referenced with `@tags`.
- Versioned PNG reference assets organized into named slots.
- Prompt resolution with missing-Element and incomplete-slot warnings.
- A typed marketing workflow powered by **LangGraphJS**.
- Deterministic evaluation with explainable issues and recommendations.
- Human-review routing with persisted decisions and feedback.
- Workflow checkpoints and event history in SQLite.
- Draft-provider abstraction with a safe local deterministic provider.
- MCP tools over stdio and authenticated Streamable HTTP.
- REST endpoints and a local dashboard for operating the workflow.
- Local filesystem or S3-compatible asset storage.
- Encrypted provider credentials with masked API-key responses.
## Product flow
```text
Creative references + marketing brief
↓
Resolve @Element context
↓
Create a draft
↓
Evaluate issues and risks
↓
Complete or request human review
```
## Current workflow
The runnable vertical slice is:
```text
Marketing brief
→ normalizeBrief
→ loadBrandContext
→ createDraft
→ evaluateDraft
→ decideNextStep
→ completed / waiting_for_human / failed
```

The graph is implemented in [`src/workflows/marketing-graph.ts`](src/workflows/marketing-graph.ts) with LangGraphJS `StateGraph`. Each business node follows this application-owned contract:
```text
(state, context) → partial state update
```
### Node responsibilities
| Node | Responsibility |
|---|---|
| `normalizeBrief` | Trim and validate objective, audience, channel, and request. |
| `loadBrandContext` | Resolve `@Element` tags and collect assets, missing tags, and completeness warnings. |
| `createDraft` | Call the injected `DraftProvider`; the default provider creates a deterministic local draft. |
| `evaluateDraft` | Check unknown Elements and missing required slots. |
| `decideNextStep` | Complete clean runs or create a pending `ReviewTask`. |
Every node checkpoint is persisted. The latest workflow state and the ordered event history can be inspected after a restart.
## Architecture
MVR is a modular monolith. HTTP and MCP are adapters; application behavior is kept behind domain and application modules.
```mermaid
flowchart TD
User[Marketing team]
Web[Local dashboard]
Client[MCP-compatible AI client]
REST[REST adapter]
MCP[MCP adapter]
Service[MarketingWorkflowService]
Graph[LangGraphJS StateGraph]
Vault[(SQLite Vault)]
Assets[(Local or S3 assets)]
Provider[Injected DraftProvider]
User --> Web
User --> Client
Web --> REST
Client --> MCP
REST --> Service
MCP --> Service
Service --> Graph
Graph --> Vault
Graph --> Provider
Vault --> Assets
```
### Main modules
| Module | Role |
|---|---|
| `src/index.ts` | HTTP server, dashboard route, REST routes, and stdio entry point. |
| `src/mcp.ts` | MCP tool definitions and MCP adapter. |
| `src/dashboard.ts` | Self-contained local HTML dashboard. |
| `src/vault.ts` | SQLite facade, migrations, persistence, asset coordination, and event history. |
| `src/storage.ts` | Local and S3-compatible `AssetStorage` implementations. |
| `src/crypto.ts` | AES-GCM provider-secret encryption and masking. |
| `src/workflows/marketing-graph.ts` | Typed workflow state, nodes, graph assembly, and runner. |
| `src/workflows/marketing-service.ts` | Shared application boundary for REST, MCP, and web clients. |
| `src/workflows/draft-provider.ts` | Draft provider contract and deterministic local implementation. |
### Boundary rules
1. REST and MCP handlers call `MarketingWorkflowService`, not graph internals.
2. Business logic is not duplicated between adapters.
3. `Vault` owns SQLite-backed persistence coordination.
4. `AssetStorage` hides local filesystem and S3 implementation details.
5. Provider secrets are encrypted at rest and never returned in plaintext.
6. Evaluation returns explainable issues, not only a score.
7. Missing creative context is surfaced as a warning and can route to review.
8. External generation providers must be accessed through an adapter.
9. Do not add queues, microservices, PostgreSQL, or vector infrastructure without measured operational requirements.
## Core concepts
### Elements
An Element is a reusable creative reference:
- `character`
- `prop`
- `location`
- `style`
- `audio`
- `other`
Example:
```text
Create a product video featuring @hero and @headphones.
```
An Element can have versioned assets in slots:
```text
@hero
├── face_closeup
├── body_front
└── body_back
```
Kind templates define required and optional slots. A character, for example, requires `face_closeup`, `body_front`, and `body_back`. Missing required slots produce completeness warnings.
### Prompt resolution
`resolve_prompt` and `Vault.resolvePrompt()` replace known `@tags` with reusable context and expose:
- `resolvedText`
- `elementsUsed`
- attached reference assets
- `missing` unknown tags
- `completenessWarnings`
Unknown or incomplete context is not silently discarded.
### Workflow state
A workflow run is persisted under a caller-provided or generated `runId`.
The state contains:
- marketing brief
- resolved prompt context
- draft
- draft provenance (`provider`, `model`, `generatedAt`)
- evaluation result
- human-review requirement
- structured `ReviewTask`
- current node
- status and errors
Statuses are:
```text
running
completed
waiting_for_human
failed
```
The `workflow_events` table stores ordered node snapshots such as:
```text
start
normalizeBrief
loadBrandContext
createDraft
evaluateDraft
decideNextStep
```
### Draft providers
The graph depends on the `DraftProvider` interface rather than a specific model vendor:
```ts
interface DraftProvider {
createDraft(request: DraftRequest): Promise<DraftResult>;
}
```
The default `DeterministicDraftProvider` is intentionally local and predictable. It does not call an external model. A real provider can be injected through `MarketingWorkflowService` without changing the graph or adapters.
### Evaluation
The current evaluator is deterministic and checks:
- unknown `@Elements`
- incomplete required Element slots
A clean context receives score `1` and completes. Incomplete context receives score `0.5` and waits for human review. Future evaluators can add brand voice, channel fit, factuality, compliance, and model-based judging while retaining the deterministic gate.
## Dashboard
The local dashboard is served from `/` and supports:
- create and delete Elements
- inspect required and optional slots
- upload versioned PNG assets
- lock an asset slot after approval
- save encrypted provider credentials
- configure task-specific models
- submit a marketing brief
- inspect workflow status, draft, evaluation, errors, and provenance
- inspect checkpoint history
- approve or reject pending human review with feedback
Open it after starting the server:
```text
http://localhost:8080
```
## MCP tools
The server supports MCP over stdio and authenticated Streamable HTTP.
### Creative library
| Tool | Purpose |
|---|---|
| `list_elements` | List reusable Elements. |
| `get_element` | Get an Element and version history. |
| `create_element` | Create a character, prop, location, style, audio, or other Element. |
| `update_element` | Update Element metadata or summary. |
| `delete_element` | Delete an Element and its records. |
| `upload_element_asset` | Upload a PNG to a named Element slot. |
| `list_element_assets` | List the current asset in each filled slot. |
| `check_element_completeness` | Check required slots against the kind template. |
| `get_kind_template` | Get slot recipes and best practices. |
| `get_prompt_format` | Get a provider/model prompt format template. |
| `resolve_prompt` | Resolve `@tags` into context and asset references. |
### Workflow
| Tool | Purpose |
|---|---|
| `run_marketing_workflow` | Run and persist the marketing graph. |
| `get_workflow_run` | Read one persisted workflow run. |
| `list_workflow_runs` | List runs ordered by recent update. |
| `get_workflow_events` | Read ordered node checkpoint history. |
| `resume_marketing_workflow` | Approve or reject a pending human review. |
### Provider and model configuration
| Tool | Purpose |
|---|---|
| `list_model_configs` | List task-specific model configurations. |
| `set_model_config` | Save a provider/model configuration. |
| `get_active_model` | Get the active model for a task. |
| `delete_model_config` | Delete a model configuration. |
| `list_providers` | List providers with masked API keys. |
Example workflow input:
```json
{
"objective": "Introduce the campaign hero",
"audience": "Existing customers",
"channel": "social",
"request": "Create a short post featuring @hero."
}
```
## REST API
All REST routes are under `/api`. If `MVR_AUTH_TOKEN` is configured, they require a Bearer token.
| Method | Endpoint | Purpose |
|---|---|---|
| `GET`, `POST` | `/elements` | List or create Elements. |
| `DELETE` | `/elements/:tagOrId` | Delete an Element. |
| `GET`, `POST` | `/elements/:tagOrId/assets` | List or upload Element assets. |
| `GET` | `/elements/:tagOrId/completeness` | Check required slots. |
| `GET` | `/kind-templates/:kind` | Get an Element kind template. |
| `GET` | `/prompt-formats?provider=Seedance&model=2.0` | Get a prompt format. |
| `GET`, `POST` | `/providers` | List or save encrypted provider credentials. |
| `DELETE` | `/providers/:name` | Delete a provider. |
| `GET`, `POST` | `/model-configs` | List or save model configurations. |
| `DELETE` | `/model-configs/:task` | Delete a model configuration. |
| `POST` | `/workflows/marketing` | Start a workflow run. |
| `GET` | `/workflows/marketing` | List workflow runs. |
| `GET` | `/workflows/marketing/:runId` | Read a workflow run. |
| `GET` | `/workflows/marketing/:runId/events` | Read checkpoint/event history. |
| `POST` | `/workflows/marketing/:runId/review` | Approve or reject human review. |
Example:
```bash
curl -X POST http://localhost:8080/api/workflows/marketing \
-H 'Content-Type: application/json' \
-d '{
"objective": "Launch the hero",
"audience": "Existing customers",
"channel": "social",
"request": "Create a post featuring @hero."
}'
```
## Quickstart
### Local
```bash
cp .env.example .env
# Set MVR_ENCRYPTION_KEY and MVR_AUTH_TOKEN in .env.
npm ci
npm run build
npm start
```
The server listens on `0.0.0.0:8080` by default.
Development mode:
```bash
npm run dev
```
Run tests and build:
```bash
npm test
npm run build
```
Run the stdio MCP server:
```bash
npm run stdio
```
### Docker
```bash
cp .env.example .env
# Set MVR_ENCRYPTION_KEY and MVR_AUTH_TOKEN.
docker compose up --build
```
Docker Compose persists SQLite data in the `mvr-data` volume and binds local assets through `./data/assets`.
### MCP client configuration
For a stdio-compatible client:
```json
{
"mcpServers": {
"mvr": {
"command": "node",
"args": ["/absolute/path/to/MVR/dist/index.js", "--stdio"],
"env": {
"MVR_DB_PATH": "/absolute/path/to/MVR/data/mvr.db",
"MVR_ASSET_PATH": "/absolute/path/to/MVR/data/assets",
"MVR_ENCRYPTION_KEY": "your-long-random-secret"
}
}
}
}
```
For Streamable HTTP:
```text
http://localhost:8080/mcp
Authorization: Bearer your-long-random-token
Accept: application/json, text/event-stream
```
## Configuration
| Variable | Purpose | Default |
|---|---|---|
| `PORT` | HTTP port. | `8080` |
| `MVR_AUTH_TOKEN` | Bearer token for HTTP and MCP. | unset |
| `MVR_ENCRYPTION_KEY` | Key for encrypted provider credentials. | development fallback; set in production |
| `MVR_DB_PATH` | SQLite database path. | `data/mvr.db` |
| `MVR_STORAGE_DRIVER` | `local` or `s3`. | `local` |
| `MVR_ASSET_PATH` | Local asset root. | `data/assets` |
| `MVR_ASSET_IMPORT_PATH` | Trusted local PNG import directory. | `data/imports` |
| `MVR_S3_BUCKET` | S3-compatible bucket. | unset |
| `MVR_S3_REGION` | S3-compatible region. | `us-east-1` |
| `MVR_S3_ACCESS_KEY_ID` | S3-compatible access key. | unset |
| `MVR_S3_SECRET_ACCESS_KEY` | S3-compatible secret. | unset |
| `MVR_S3_ENDPOINT` | Optional S3-compatible endpoint. | unset |
| `MVR_MIN_EVAL_RUNS_BEFORE_AUTO_UPDATE` | Reserved threshold for future automatic recipe updates. | `10` |
## Security and operational notes
- Set `MVR_AUTH_TOKEN` before exposing the server beyond localhost.
- Set a high-entropy `MVR_ENCRYPTION_KEY` in production and back it up securely.
- Changing the encryption key makes existing encrypted credentials unreadable.
- Provider keys are encrypted at rest and masked in responses.
- Only valid PNG assets up to 10 MB are accepted.
- File references are constrained to `MVR_ASSET_IMPORT_PATH`.
- Asset versions are preserved; locked slots cannot be overwritten.
- Use the human-review route before publishing or taking irreversible actions.
- The current local dashboard is intended for a trusted local workspace; multi-user authorization is not implemented by default.
## Tests
The test suite covers:
- Element versioning and prompt resolution.
- Provider encryption and masking.
- Model configuration.
- Asset versioning, PNG validation, and locking.
- Kind templates and completeness warnings.
- Successful, failed, and human-review workflow paths.
- LangGraph execution and node checkpoints.
- Draft-provider injection and provenance.
- Workflow event history.
- Idempotent run IDs and brief-conflict protection.
- SQLite persistence across vault reopen.
Run:
```bash
npm test
npm run build
```
## Roadmap
### Next
- Replace the deterministic provider with external LLM/video provider adapters.
- Add prompt/model version hashes and richer generation provenance.
- Use LangGraph interrupt/checkpointer resume semantics for a true durable human-in-the-loop pause.
- Add optimistic locking and stronger concurrency controls.
- Add brand, campaign, and brief entities.
- Add brand voice, compliance, factuality, and channel-fit evaluators.
### Later
- Bounded revise loops after human feedback.
- Multiple AI provider adapters.
- Integrations for Slack, Notion, Drive, Figma, HubSpot, and analytics.
- Golden examples and regression evaluation datasets.
- Brand drift detection and campaign-performance feedback.
- Multi-user roles and approval policies.
## Scope boundary
MVR currently does not:
- generate videos or images by itself
- replace external AI generation providers
- automatically publish marketing content
- automatically modify brand rules from model output
- provide multi-user authorization by default
## License
See the repository license for usage terms.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues