Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.propal.io/llms.txt

Use this file to discover all available pages before exploring further.

The MCP exposes the same theming model the editor uses — palette + ~3 section ambiences + typography + radii — through a layered set of tools that match the agent’s intent.

The three layers

LayerWhat it controlsWhere it’s stored
ThemePage palette, fonts, radius, plus a list of “section ambiences”themes table (per org)
SectionWhich ambience this section adopts (section_style), plus optional background imagedata-* attrs on the TipTap section node
PageFallback when a section opts out (section_style: 'default')The theme’s page-level tokens
A proposal references a theme via theme_id. If absent, it falls back to a legacy proposal_style jsonb on the proposal row.

Three modes of creating a theme

1. Preset (assisted)

Pick from the catalog and snapshot it. Lowest effort — the catalog has ~25 curated palettes (matcha, ocean, ink, midnight, …) covering most use cases.
list_design_presets
→ [{ id: 'matcha', sample: { pageBg, accent, sectionBgs }, fonts, radius }, ...]

create_theme_from_preset
  preset_id: 'matcha'
  name: 'Acme matcha'
  overrides: { titleFont: 'satoshi' }     // optional

2. Smart palette (generated)

Provide a mini-palette, the server generates a full WCAG-compliant theme via chroma-js — including accent solid/subtle variants, badges, and 3 section ambiences automatically derived from the section backgrounds.
generate_theme_from_palette
  name: 'Brand 2026'
  page_bg: '#FAFBFE'
  accent: '#2563EB'
  section_bgs: ['#EEF2FF', '#1E293B', '#D4DCF0']  // optional, 1–3 entries
  title_font: 'inter'
  body_font: 'inter'
  radius: 0.8
  button_radius: 0.5

→ { theme: { id, ... }, warnings: [{ pair, contrast, message }] }
The same input can be validated without persisting:
validate_palette
  page_bg: '#FAFBFE'
  accent: '#FFFF00'   // bad: low contrast on white
→ { ok: false, warnings: [{ pair: 'accent / page_bg', contrast: 1.07, ... }] }

3. Advanced (full control)

When the agent needs to set every token explicitly — e.g. matching a brand guideline that doesn’t fit smart-mode generation — use create_theme with the typed colors and section_styles arrays.
create_theme
  name: 'Bespoke 2026'
  title_font: 'plus_jakarta'
  body_font: 'inter'
  radius: 1.2
  button_radius: 0.5
  colors: {
    bg: '#0E0E12',
    headingColor: '#F5F5F7',
    textColor: '#A1A1A6',
    cardBg: '#16161B',
    cardHeadingColor: '#F5F5F7',
    cardTextColor: '#C7C7CC',
    cardBorderColor: '#1E1E24',
    accentColor: '#7B2D3B',
    accentBg: '#2E1318',
    accentContrast: '#FFFFFF',
    accentSolidBg: '#7B2D3B',
    accentSolidText: '#FFFFFF',
    accentSubtleBg: '#2E1318',
    accentSubtleText: '#F5F5F7'
  }
  section_styles: [
    {
      id: 'soft',           // ← optional but recommended
      bg: '#16161B', headingColor: '#F5F5F7', textColor: '#C7C7CC',
      borderColor: '#1E1E24',
      cardBg: '#1E1E24', cardHeadingColor: '#F5F5F7',
      cardTextColor: '#C7C7CC', cardBorderColor: '#2A2A33'
    },
    { id: 'deep', /* ... */ },
    { id: 'highlight', /* ... */ }
  ]
Patch later with update_theme (full PATCH semantics) or with the more targeted update_theme_section_style:
update_theme_section_style
  theme_id: '<uuid>'
  slot_index: 0           // 0-based — replaces section_styles[0]
  tokens: { bg: '...', ... }
Each entry in section_styles[] accepts an optional id (1-32 chars, [a-zA-Z][a-zA-Z0-9_-]*). Reserved values: "default" and any digit-only string. Ids must be unique per theme. When sections reference a slot by id (section_style: "deep"), the server resolves it to the positional index before assembly. Survives reordering: if you later reorganize section_styles[], sections that use named refs keep pointing at the same ambience. Sections that use positional refs ("1", "2") silently shift. The reverse mapping is also done in get_proposal_tree: positional refs are converted back to ids in the response when ids exist.

Apply / detach

apply_theme_to_proposal
  proposal_id: '<uuid>'
  theme_id: '<uuid>'
detach_theme_from_proposal
  proposal_id: '<uuid>'
  snapshot_style: true      // default true: preserve current rendering
                            // by writing the resolved style to proposal_style
If snapshot_style: false, the proposal falls back to its legacy proposal_style (or defaults if absent) — useful when you want to “reset” a proposal to a different visual baseline.

Read the resolved style

get_proposal_resolved_style
  proposal_id: '<uuid>'
→ {
    titleFont, bodyFont,
    radius, buttonRadius,
    colors: { ...IColorTokens },
    sectionStyles: ISectionStyleTokens[],
    // legacy fields surfaced if they apply
  }
The resolution chain:
  1. If proposal.theme_id is set → build the style from the theme row.
  2. Else if proposal.proposal_style is set → parse it.
  3. Else → defaults from @propal/core/theming.

Per-section overrides

Sometimes you want one section to break the theme — e.g. a hero with a background image and a different ambience. Use set_proposal_section_attrs:
set_proposal_section_attrs
  proposal_id: '<uuid>'
  index: 0                      // positional, refetch via get_proposal_tree
  attrs: {
    section_style: 'deep',      // named ref → resolved server-side
    background_image: {
      media_id: '<asset uuid>', // must exist in the org's media library
      overlay: true,
      overlay_opacity: 60,
      size: 'cover',
      position_x: 'center',
      position_y: 'top'
    }
  }
This is a patch — only the keys you provide are merged onto the section’s attrs. Children are untouched.

Tips

  • Generate first, refine later. generate_theme_from_palette produces WCAG-safe results in seconds. Use update_theme_section_style to tweak individual slots if needed.
  • Always name your slots. It’s a 5-character habit (id: "deep") that saves debugging when you reorder slots later.
  • Check the warnings. generate_theme_from_palette returns WCAG warnings inline — surface them to the user before persisting if contrasts are weak.
  • Don’t mix advanced and smart in one go. If you start with generate_theme_from_palette and want to tweak, prefer update_theme_section_style for surgical changes; full update_theme PATCH still works but rewrites are noisier.