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.

Component DSL limits

Exposed in the limits field of list_propal_components:
LimitValueMeaning
MAX_SECTIONS10Sections per single append call
MAX_DEPTH6Max nesting depth (columns inside columns…)
MAX_NODES200Total nodes per tree (assembler-side count)
MAX_BLOCKS_PER_SECTION50Direct children of a single section
MAX_BLOCKS_PER_COLUMN30Children inside one column of a columns block
MAX_COLUMNS4columns.cols upper bound
If the agent exceeds a limit, validation rejects the tree with an explicit error like tree exceeds MAX_NODES (200).

Structural rules

Enforced both by Zod and a runtime validator (so you get a clear message, not a generic Zod failure):
  • Root must be a section or section[]
  • A section cannot contain another section
  • columns.cols must equal columns.length
  • embed.src must match the provider’s host allowlist (HTTPS only)
  • image / icon / video cannot have a src (placeholders only)

Theme validation

  • themes.section_styles[].id is optional, but if present must be 1-32 chars matching ^[a-zA-Z][a-zA-Z0-9_-]*$.
  • Reserved ids (rejected): "default" and any digit-only string.
  • Ids must be unique within a single theme — duplicates → BAD_REQUEST.
  • apply_theme_to_proposal checks the theme belongs to the org; otherwise → NOT_FOUND.

Error envelope

MCP errors return as the standard MCP error result:
{
  "isError": true,
  "content": [{ "type": "text", "text": "<message>" }]
}
Common messages by category:

Validation

  • Invalid component tree: <path>: <reason>
  • section cannot be nested inside another section
  • embed.src host does not match provider "<provider>"
  • tree exceeds MAX_NODES (200)

Authorization

  • Missing required scope: mcp:use
  • Proposal not found — usually means the ID doesn’t belong to the user’s current organization (cross-org access is blocked).

Concurrency

  • Proposal changed too quickly. Please retry the append. — 3 consecutive optimistic-update conflicts. Refetch via get_proposal_tree and retry.

Out-of-bounds

  • index <N> is out of bounds (must be 0..<max>)
  • order length <N> does not match document length <M>
  • order contains duplicate index <N>

Theme reference

  • section.section_style "<name>" is not a known slot id in the applied theme. Available ids: "soft" (1), "deep" (2), "3".

Rate limiting

The MCP server enforces the same per-organization rate limits as the REST API. Bursts are smoothed; sustained throughput is capped. If you exceed the limit, the response includes a Retry-After hint and the agent should back off.

Concurrency model recap

Every mutation tool uses optimistic concurrency on proposals.updated_at:
  1. Snapshot the doc + updated_at.
  2. Apply the mutation locally.
  3. UPDATE … WHERE updated_at = <snapshot> — guard.
  4. On conflict (someone else wrote): refresh + retry, up to 3 times.
  5. On 3 consecutive conflicts: surface the error to the agent.
Reads (get_proposal_tree, get_proposal_resolved_style) don’t take a lock — they reflect the current row.

Best practices

1

Refetch between mutations

Indices shift after insert / update (1 → N) / delete / reorder. find_and_replace_in_proposal is the exception — it doesn’t change the structure.
2

Validate before applying when chaining

validate_propal_component_tree lets you check a tree without writing. Useful for dry-runs and error explanation to the user.
3

Use named section_style ids

section_style: "deep" survives reordering of the theme’s slots. "2" silently shifts when slots are reordered.
4

Honor WCAG warnings

generate_theme_from_palette returns warnings — surface them to the user before persisting if contrasts are weak.
5

Surface conflicts gracefully

On Proposal changed too quickly, refetch and let the agent decide (retry, merge, abort).