vesper Svelte Themes

Vesper

A lightweight CMS written in TypeScript, leveraging Svelte 5 with SvelteKit and NestJS. Focus on AI generated content with the possibility to manually update content via an admin panel

Vesper

A lightweight, white-label, plugin-extensible CMS built with TypeScript. AI-first content creation via an MCP server, with a full admin panel for manual editing.

Stack

Layer Technology
API NestJS 11, MikroORM 6, PostgreSQL 16
Frontend Svelte 5 + SvelteKit 2 (SSR, adapter-node)
Editor TipTap (ProseMirror)
Auth JWT access tokens + opaque refresh tokens, API keys
Packages pnpm workspaces
Infrastructure Docker Compose

Monorepo layout

apps/
  api/              NestJS backend
  web/              SvelteKit admin + frontend
packages/
  shared/           Zod schemas and TypeScript types  (@vesper/shared)
  plugin-sdk/       Plugin interface definitions       (@vesper/plugin-sdk)
  theme-sdk/        CSS variable contracts + defaults  (@vesper/theme-sdk)
  mcp-server/       MCP server for AI content tools    (@vesper/mcp-server)
plugins/
  example-plugin/   Reference plugin implementation
docker/
  docker-compose.yml        Production
  docker-compose.dev.yml    Dev infrastructure (Postgres + Redis)
  Dockerfile.api
  Dockerfile.web

Getting started

Prerequisites: Node 22+, pnpm 10+, Docker

# One-shot setup (detects and avoids port conflicts automatically)
make setup

# Seed the database (creates [email protected] / admin1234)
make seed

# Start the dev servers
make dev

Or step by step without Make:

# 1. Start dev infrastructure (Postgres + Redis)
docker compose -f docker/docker-compose.dev.yml up -d

# 2. Install dependencies
pnpm install

# 3. Build shared packages (required before starting the API)
pnpm --filter @vesper/shared build
pnpm --filter @vesper/plugin-sdk build
pnpm --filter @vesper/theme-sdk build

# 4. Copy and fill in environment variables
cp .env.example .env

# 5. Run database migrations
pnpm --filter @vesper/api migration:up

# 6. Start both apps in parallel
pnpm dev

The API will be available at http://localhost:3000/api/v1 and the admin at http://localhost:5173.

Common commands

pnpm build                               # Build all packages and apps
pnpm test                                # Run all unit tests
pnpm lint                                # Lint everything
pnpm typecheck                           # Type-check all packages

pnpm --filter @vesper/api dev            # API only (watch mode)
pnpm --filter @vesper/web dev            # Web only (watch mode)

pnpm --filter @vesper/api migration:create   # Create a new migration
pnpm --filter @vesper/api migration:up       # Run pending migrations

Features

  • Headless content API — define custom content types with a schema builder; content is stored as structured JSON
  • Multi-domain / multi-site — run one Vesper instance for any number of websites; each domain gets its own scoped content and theme
  • Rich-text editor — TipTap with tables, links, images, and formatting
  • SEO — auto-generated meta, Open Graph, and structured data per content item with a SERP preview panel
  • Media library — file uploads with image dimension extraction via sharp
  • Plugin system — five extension points: content types, API routes, admin UI panels, event hooks, theme overrides
  • Theming — full HSL-based CSS custom property system via @vesper/theme-sdk; per-site themes with global fallback
  • MCP server — AI agents can manage sites, content types, content, and themes via the Model Context Protocol
  • API keys — scoped machine tokens with bcrypt-hashed storage and prefix-based lookup
  • Role-based accessadmin, editor, viewer roles enforced by NestJS guards

Multi-domain / multi-site

A single Vesper deployment can serve any number of websites. Each site is registered with a domain name and gets its own content and theme.

How it works

  1. Register a site — go to Admin → Sites and add a domain (e.g. blog.acme.com).
  2. Assign content — when creating or editing a content entry, pick the site in the sidebar. Content is only served to requests coming from the matching domain.
  3. Per-site themes — each domain can have its own theme. A site-specific theme is created from the global defaults on the first set_theme call for that domain.
  4. Domain routing — the SvelteKit frontend reads the incoming Host header and forwards it as X-Vesper-Site to the API. The API scopes queries to the matching site automatically.
  5. Admin is unscoped — the admin panel shows all content across all sites so editors can manage everything in one place.

Unknown domains receive empty content lists (not a fall-through to global content), ensuring strict isolation between sites.

Plugin development

Implement the CmsPlugin interface from @vesper/plugin-sdk:

import type { CmsPlugin } from '@vesper/plugin-sdk'

const plugin: CmsPlugin = {
  name: '@my-org/my-plugin',
  version: '1.0.0',
  async setup(ctx) {
    ctx.events.on('content.published', (e) => console.log('published', e))
  },
  routes: [{ method: 'GET', path: '/hello', handler: (req, res) => res.json({ ok: true }) }],
}

export default plugin

Register the plugin by adding its file path to the plugins config array.

MCP server

The MCP server exposes CMS tools and resources to AI agents over stdio, enabling programmatic content and theme management.

Starting the server

CMS_API_URL=http://localhost:3000/api/v1 \
CMS_API_KEY=ck_your_key \
node packages/mcp-server/dist/index.js

Available tools

Site Management

  • list_sites — List all registered sites (domains)

  • create_site — Register a new site

    • name (required): Human-readable label (e.g. "Acme Corp Blog")
    • domain (required): Hostname without protocol (e.g. "blog.acme.com")
    • description (optional): What this site is for
  • update_site — Update an existing site

    • id (required): Site UUID
    • name, domain, description, isActive (all optional)
  • delete_site — Delete a site. Content assigned to this site becomes unassigned.

    • id (required): Site UUID

Content Discovery & Inspection

  • list_content_types — List all available content types with their schemas and metadata

Content Operations

  • list_content — List content entries of a specific type. Supports filtering by status, site, and pagination

    • type (required): Content type slug
    • status (optional): Filter by publication status (draft, published, archived)
    • siteId (optional): Filter to only content assigned to this site
    • page (optional, default: 1): Pagination page
    • limit (optional, default: 20): Results per page
  • create_content — Create a new content entry

    • type (required): Content type slug
    • data (required): Content fields matching the schema
    • slug (optional): URL-friendly slug
    • locale (optional): Locale code (e.g., "en")
    • siteId (optional): Assign to a specific site by UUID
  • update_content — Update an existing content entry by ID

    • type (required): Content type slug
    • id (required): Content entry UUID
    • data (optional): Partial fields to update
    • slug (optional): Updated URL slug
    • siteId (optional): Assign to a site (UUID) or null to unassign
  • publish_content — Publish a draft content entry (changes status to published)

    • type (required): Content type slug
    • id (required): Content entry UUID

SEO & Metadata

  • generate_seo — Auto-generate SEO metadata (title, description, Open Graph tags) for a content entry
    • contentId (required): Content entry UUID

Theming

  • get_theme — Retrieve the theme/styling configuration

    • domain (optional): Domain to get site-specific theme for (e.g. "blog.acme.com"). Omit for global theme.
    • Returns: Full ThemeSettings object including:
      • id: Theme settings UUID
      • config: Theme colors, typography, and design tokens
      • contentTypeStyles: Per-content-type custom CSS
      • globalCustomCss: Global custom CSS applied to all pages
      • localFontCss: @font-face CSS for locally-hosted fonts (GDPR-compliant alternative to Google Fonts)
      • updatedAt: Timestamp of last update
  • set_theme — Update theme configuration. Requires admin credentials. Supports partial updates for most fields

    • config (optional): Partial theme config object. Nested objects (e.g., light, dark, typography) are merged. Example: { light: { primary: "221 83% 53%" } }
    • contentTypeStyles (optional): Per-content-type custom CSS as { typeSlug: cssString }. Note: This replaces the entire contentTypeStyles object; it is not merged per-type
    • globalCustomCss (optional): Global custom CSS applied to all public pages
    • domain (optional): Domain to update site-specific theme for (e.g. "blog.acme.com"). Omit to update global theme.

Content Type Management

  • create_content_type — Create a new content type schema

    • name (required): Human-readable name
    • slug (required): Unique lowercase + hyphens identifier
    • fields (required): Array of field definitions
    • description (optional)
  • delete_content_type — Soft-delete a content type by slug

    • slug (required): Content type slug

Resources

The server exposes content type schemas as MCP resources (cms://content-type/{slug}), allowing AI agents to introspect exact field structures before creating or updating content.

Website Agent (@vesper/website-agent)

The website agent is a CLI tool and importable package that uses Claude (via the Anthropic API) to build complete, production-ready websites inside Vesper from a single natural-language description. It connects to the Vesper MCP server over stdio and drives every step — registering the site, creating content types, writing copy, applying a theme, and publishing — automatically.

Prerequisites

  • Vesper API running and accessible
  • An Anthropic API key
  • A Vesper API key with admin scope (create one in Admin → API Keys)
  • The MCP server built: pnpm --filter @vesper/mcp-server build
  • The agent built: pnpm --filter @vesper/website-agent build

Environment variables

Variable Required Default Description
ANTHROPIC_API_KEY Yes Anthropic API key
CMS_API_KEY Yes Vesper admin API key
CMS_API_URL No http://localhost:3001/api/v1 Vesper API base URL
VESPER_MODEL No claude-opus-4-8 Claude model to use
VESPER_MCP_PATH No auto-detected Path to the compiled MCP server index.js
VESPER_QUIET No Set to "1" to suppress verbose output

Running as a CLI

After building, the vesper-agent binary is available in the package's dist/:

# Build dependencies first (once)
pnpm --filter @vesper/mcp-server build
pnpm --filter @vesper/website-agent build

# Run
export ANTHROPIC_API_KEY=sk-ant-...
export CMS_API_KEY=ck_your_vesper_key

node packages/website-agent/dist/index.js "Create a portfolio website for a photographer named Alex Chen"

Or via pnpm:

pnpm --filter @vesper/website-agent dev "Create a SaaS landing page for a project management tool"

What the agent does

The agent follows a deterministic eight-step workflow for every run:

  1. Inspect — calls list_content_types and list_sites to understand the current CMS state
  2. Register the site — calls create_site with the inferred domain and name if the site doesn't exist yet
  3. Create content types — calls create_content_type for each type the site needs (pages, blog posts, team members, testimonials, FAQs, nav items, etc.) with appropriate field schemas
  4. Create content — calls create_content for each entry, writing real copy (never Lorem Ipsum) and assigning every entry to the site via siteId
  5. Set the theme — calls get_theme then set_theme to apply a brand-appropriate HSL color palette and any custom CSS
  6. Generate SEO — calls generate_seo for every content entry to auto-populate meta tags and Open Graph data
  7. Publish — calls publish_content for every entry so all content is live
  8. Report — prints a summary of everything created

Using as a library

runWebsiteAgent can be imported directly for embedding in your own tooling:

import Anthropic from '@anthropic-ai/sdk'
import { VesperMcpClient } from '@vesper/website-agent/dist/mcp-client.js'
import { runWebsiteAgent } from '@vesper/website-agent/dist/agent.js'

const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY })
const mcpClient = new VesperMcpClient(
  'http://localhost:3001/api/v1',
  process.env.CMS_API_KEY,
  '/path/to/mcp-server/dist/index.js',
)

await mcpClient.connect()
await runWebsiteAgent('A restaurant website for Bella Cucina in Rome', mcpClient, anthropic, {
  model: 'claude-opus-4-8',
  verbose: true,
})
await mcpClient.close()

runWebsiteAgent options:

Option Type Default Description
model string 'claude-opus-4-8' Claude model ID
maxTokens number 8192 Max tokens per Claude response
verbose boolean true Print tool calls and agent text to stdout

Limits and safety

  • The agent stops after 60 tool-call iterations to prevent runaway runs
  • Tool results larger than 8 000 characters are truncated before being fed back to the model
  • The agent handles max_tokens continuations automatically (up to 5 times) when the model hits its token budget mid-response
  • If a tool call fails, the agent reports the error back to the model and lets it adapt rather than crashing

Docker

# Production stack
docker compose -f docker/docker-compose.yml up -d

# Build images locally
docker build -f docker/Dockerfile.api .
docker build -f docker/Dockerfile.web .

Environment variables

See .env.example for the full list. Required for production:

  • JWT_SECRET — 64+ hex chars (openssl rand -hex 64)
  • JWT_REFRESH_SECRET — separate 64+ hex chars
  • DB_PASSWORD

Top categories

Loading Svelte Themes