The MCP exposes a complete read/edit toolkit on top of the components DSL. An agent can walk an existing proposal, target sections by index, mutate them atomically, and re-fetch as needed.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 lifecycle
Read
kind: 'section'— the disassembler produced a DSL section.dslis the authoring shape used byinsert/update.kind: 'other'— a root-level node that isn’t a section (legacy or manual edit).fallbackcarries enough info (text preview, child count) to let the agent decide whether to delete or replace it.
fallback. To preserve a complex node, prefer find_and_replace or
delete_section_at_index + a fresh insert.
Reverse mapping niceties
- Old proposals containing
testimonialsContainer/accordionContainer/ etc. are flattened to the grouped DSL form (testimonial_cards,questions, …) on read. - Section slots referenced positionally are converted back to named ids if
the applied theme has them (
section_style: "2"→"deep"). - Assembler defaults are stripped on read so DSL ↔ TipTap ↔ DSL is a fixed point for every component the DSL covers.
Insert
append_propal_component_tree_to_proposal,
resolves named section_style refs against the proposal’s theme, then
inserts BEFORE index.
Update
index by the assembled section(s). 1 → N is supported
— useful when you want to split a single section into several.
Delete
kind: 'other' items too.
Reorder
order MUST contain every integer in [0, length-1] exactly once and have
the same length as the document. The new arrangement matches the order of
values in order: new[0] = old[2], new[1] = old[0], etc.
Find & replace
- Literal matching only — no regex.
- Walks all
textnodes recursively, preserving marks (bold, italic, …). - Short-circuits when 0 replacements — no Supabase write, no
updated_atbump. - Whole-word matching is Unicode-aware (
\p{L}\p{N}_boundaries) so French accents work correctly.
Patching section attrs
To change a section’s look without touching its children, use the dedicated patch tool:Concurrency
All mutation tools share the same optimistic-concurrency mechanism:Retry on conflict
If
updated_at shifted (someone else wrote concurrently), refresh and
retry. Up to 3 attempts.Pitfalls
Indices shift after mutation — always refetch
Indices shift after mutation — always refetch
insert_section_at_index(0, X) then delete_section_at_index(2) does
NOT delete the original index 2 — the original index 2 is now at
index 3 because of the insert. Always refetch via get_proposal_tree
between mutations.Do I need to refetch after find_and_replace?
Do I need to refetch after find_and_replace?
No —
find_and_replace_in_proposal doesn’t change the structure, only
text content inside nodes. Indices stay stable.Insert at index = doc.length appends
Insert at index = doc.length appends
Yes.
index = 0 prepends, index = doc.length appends, anything in
between inserts before the existing item at that position.Updating a non-section item
Updating a non-section item
update_section_at_index accepts any index — even a kind: 'other'
item. The result is a section (or several) replacing the legacy node,
which is usually what you want.Bulk operations
Bulk operations
There’s no
bulk_delete or bulk_insert. Run mutations sequentially —
the optimistic guard handles concurrent writers, but each mutation is
its own atomic operation.