docs: Etappe 2b rulings, security audit and trial feedback
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
# WP-LIT-MCP Trial: „Tischlerei Holzwerk Berger“
|
||||
|
||||
Hands-on test of the LIT MCP server as a real MCP client would use it (`initialize` → `tools/list` →
|
||||
`lit-guide` → build), against the local DDEV site `https://wordpress.ddev.site:8443` (Divi 5.2.1,
|
||||
WordPress 7.1.2, Pods inactive). Site was "Breva Map Dev" before this trial. All calls went through
|
||||
`tests/Integration/lib.sh`'s `connect`/`tool`/`ability` helpers, run inside the DDEV web container via
|
||||
throwaway scripts under `.superpowers/trial/` (deleted at the end; git-excluded). Nothing published.
|
||||
|
||||
## What was built
|
||||
|
||||
All titled with the `[Test] ` prefix, all left as drafts, as requested.
|
||||
|
||||
| Type | ID | Title | Notes |
|
||||
|---|---|---|---|
|
||||
| Page | 839 | [Test] Startseite | Hero (heading+text+button+image), 3-Leistungen-Übersicht (blurbs), Kontakt-CTA |
|
||||
| Page | 834 | [Test] Leistungen | Hero, 3 service blurbs, CTA section |
|
||||
| Page | 837 | [Test] Über uns | Hero, Geschichte (image+text), 3 Werte (icon-lists; one duplicated to a 4th value) |
|
||||
| Page | 831 | [Test] Kontakt | Hero, Adresse+Kontaktformular, Öffnungszeiten (added afterwards via `lit-divi-insert`) |
|
||||
| Post | 854 | [Test] Drei Tipps zur Pflege von Massivholzmöbeln | |
|
||||
| Post | 856 | [Test] Einblick in unsere Werkstatt | text+image |
|
||||
| Post | 858 | [Test] Nachhaltigkeit beginnt beim Holz | |
|
||||
| Menu | 22 | [Test] Hauptmenü | assigned to `primary-menu` location in the same `lit/menu-create` call; 4 items (Startseite, Leistungen, Über uns, Kontakt) via `lit/menu-set-items` with `allow_unpublished: true` |
|
||||
| Media | 828, 829, 830 | Werkstatt / Team / Massivholzmöbel (Platzhalter) | imported via `lit-media-upload-from-url` from `picsum.photos` (public hotlink-friendly placeholder service — network egress from the container worked fine, no fallback to base64 needed) |
|
||||
| Site settings | – | `blogname` → "Tischlerei Holzwerk Berger", `blogdescription` → "Massivholzmöbel, Innenausbau und Restaurierung aus Berlin" | previous values (`Breva Map Dev` / empty) are in the tool's own `previous` response field if this needs reverting |
|
||||
|
||||
Front page: **not set**. `lit/wp-settings-update` correctly refused `page_on_front: 839` with
|
||||
`"839 ist keine veröffentlichte Seite."` — consistent with the server's own "Neue Website aufsetzen"
|
||||
workflow guide, which says to publish the homepage first. That step is fundamentally at odds with
|
||||
"don't publish anything," so I left `show_on_front` on `posts` and flagged it here instead of guessing
|
||||
which instruction should win.
|
||||
|
||||
Edits demonstrated with the Divi write tools (all on top of the pages above): a heading text change
|
||||
(`lit-divi-update`/`set`), a whole new section inserted (`lit-divi-insert`), a module moved across
|
||||
columns (`lit-divi-move`), a node duplicated and re-labelled (`lit/divi-duplicate-node`), and a
|
||||
site-wide text fix via `lit/divi-replace-text` with a dry run first, then a confirmed run (see friction
|
||||
log for what it was replacing).
|
||||
|
||||
## Chronological friction log
|
||||
|
||||
1. **Guide reading is thorough but not consolidated.** `lit-guide` overview lists 8 further topics
|
||||
(`content-types`, `divi-attrs`, `divi-design-system`, `divi-placeholders`, `divi-shortform`,
|
||||
`divi-structure`, `media`, `workflows`); getting the full picture before writing anything took 8
|
||||
separate `lit-guide` calls. The content itself is excellent (dense, example-driven, in German
|
||||
matching the server's own error/hint language) — no complaints about quality, only that there's no
|
||||
single "give me everything" call for a first-time client that wants to front-load context once
|
||||
rather than discover topics interactively.
|
||||
|
||||
2. **Contact-form field `type` collision (real friction, not just reading fatigue).** The
|
||||
`divi-shortform` field table lists `contact-field`'s shortform fields as `title, id, type, required`
|
||||
— but a paragraph *below* the table says `type` can't actually be set this way at insert time because
|
||||
it collides with the node-schema key `type` (the module type). I read the table, used `type: "email"`
|
||||
in a contact-field item, and got:
|
||||
`"unbekanntes Modul \"divi/input\""` / `"divi/contact-form erlaubt nur divi/contact-field als Kind"`.
|
||||
The error is technically correct (it really did try to parse `type` as a child module name) but it
|
||||
doesn't say *why* — a reader has no way to connect "unknown module divi/input" back to the
|
||||
already-read caveat about reserved field names. Fixed by omitting `type` on insert and setting it
|
||||
afterwards with `lit-divi-update`'s `set` (which *is* documented and worked first try). **Suggestion:**
|
||||
detect this specific case (a `contact-field`/`social-media-follow-network` item with a `type`/`label`
|
||||
key that isn't a valid child module name) and return a dedicated hint, e.g. "`type` on a contact
|
||||
field can't be set at insert — omit it here and set it afterwards with lit-divi-update, or use attrs
|
||||
(fieldItem.advanced.type)."
|
||||
|
||||
3. **A section can't mix `rows` and `modules`.** Tried a section with a top-level `modules` (for a
|
||||
heading) *and* `rows` (for the 3-column blurbs) — got `"divi[1]": "entweder rows oder modules
|
||||
angeben"`. Fully actionable, fixed in one edit by wrapping the heading in its own single-column row.
|
||||
Minor cost (one extra round trip), good error.
|
||||
|
||||
4. **No shortform way to link one page to another by ID.** Draft pages get an **empty** `post_name`
|
||||
until published (confirmed via direct DB read — this is normal WordPress behaviour for
|
||||
draft/pending posts, not a plugin bug), so there's no stable pretty permalink to put in a button's
|
||||
`url` field yet. The placeholder table (`divi-placeholders`) only offers `{{post:url}}` etc. for the
|
||||
*currently displayed* post, not a way to point at an arbitrary other post/page by ID. I fell back to
|
||||
raw `/?page_id=<ID>` query links for the CTA/blurb/menu targets. That works for an admin/preview
|
||||
session but is fragile (changes once published) and dead for anonymous visitors before publish.
|
||||
**Suggestion:** a placeholder like `{{link:<post_id>}}` (or reusing the `related`/attachment-ID idiom
|
||||
already used for images) that resolves through `get_permalink()` at render/save time — draft-safe now,
|
||||
automatically correct after publishing. This would remove the single biggest gap between the
|
||||
shortform and how a real multi-page site actually gets built.
|
||||
|
||||
5. **`contact-form`'s own title has no shortform heading-level control.** After building the Kontakt
|
||||
page, `lit-page-render-summary` flagged `"2 Überschriften der Ebene h1 im Inhalt."` — the
|
||||
contact-form module renders its `title` as a hard-coded `h1`, and unlike `heading`/`blurb` (which
|
||||
both expose a `level` shortform field), `contact-form` has none. Fixed by falling back to the full
|
||||
form: `lit-divi-catalog` with `module: "divi/contact-form"` to find the path, then
|
||||
`attrs: {"title.decoration.font.font": {"desktop": {"value": {"headingLevel": "h2"}}}}` — which
|
||||
worked and cleared the warning. This was the one case where the shortform genuinely wasn't
|
||||
expressive enough for a normal homepage pattern (a contact form title next to a page's own h1).
|
||||
**Suggestion:** add `level` to `contact-form`'s shortform fields (and check other title-bearing
|
||||
modules — e.g. `cta`, `map`, `slider` — for the same gap) so this doesn't require discovering the
|
||||
full-form path by trial and error.
|
||||
|
||||
6. **`preview_url` uses `http://`, `edit_url` uses `https://`, in the same response — bug.** Every
|
||||
mutating tool response on this (https-only, port-8443) site shows this mismatch, e.g. the Kontakt
|
||||
page creation returned
|
||||
`"preview_url":"http://wordpress.ddev.site:8443/?page_id=831&preview=true"` next to
|
||||
`"edit_url":"https://wordpress.ddev.site:8443/wp-admin/post.php?post=831&action=edit"`. See Bugs
|
||||
below for repro.
|
||||
|
||||
7. **Positive: error messages throughout were excellent.** `validation_failed` always came with a
|
||||
`fields` map keyed by the exact dotted input path (e.g. `divi[1].rows[0].modules[1][0].items[0].type`);
|
||||
`rev_mismatch`, `confirm_required`, and `unsupported` (tried to overwrite a Divi page's `content` via
|
||||
`lit-content-update`) all had a `hint` that named the exact next tool to call, and following it
|
||||
worked first try every time — no case where the hint was misleading or where I had to guess.
|
||||
|
||||
8. **Positive: `tree_rev` chaining removed round trips.** Every Divi write response carries the fresh
|
||||
`tree_rev`, so a sequence of edits on the same page (change heading → move a module → insert a
|
||||
section) never needed an extra `lit-divi-outline` read in between. This is the detail I'd worry the
|
||||
most about regressing — it's what makes multi-step page building on Divi tolerable turn-count-wise.
|
||||
|
||||
9. **`lit/menu-create` accepting `location` in the same call was a nice shortcut** (no separate
|
||||
`lit/menu-assign-location` round trip needed for a fresh menu). The one remaining unavoidable extra
|
||||
round trip in the menu flow was calling `lit/menu-get` immediately after `lit/menu-create` just to
|
||||
obtain `menu_rev` for the following `lit/menu-set-items` — `lit/menu-create`'s own response could
|
||||
include the initial `menu_rev` and save that call.
|
||||
|
||||
10. **`lit-divi-catalog`'s result envelope (`{modules: [...], notes: [...]}`) isn't shown anywhere** in
|
||||
the tool description or the `divi-attrs`/`divi-shortform` guides (which do embed inline JSON
|
||||
examples for almost everything else). Self-inflicted (my first `jq` filter assumed a bare array and
|
||||
errored), but a one-line example would remove the ambiguity the same way the other guides do.
|
||||
|
||||
## Prioritized improvement suggestions
|
||||
|
||||
1. **(High)** Add an internal-link placeholder for Divi shortform/full-form URL fields that resolves an
|
||||
arbitrary post/page ID to its current permalink (draft-safe), e.g. `{{link:<post_id>}}`. This is the
|
||||
most concrete gap: right now there is no supported way to cross-link draft pages that survives
|
||||
publishing, and `/?page_id=N` is the only fallback.
|
||||
2. **(High)** Give the `contact-form` module's shortform a `level` field (matching `heading`/`blurb`),
|
||||
and audit other title-bearing modules (`cta`, `map`, `slider`, …) for the same missing control —
|
||||
this is exactly the kind of heading-hierarchy mistake `lit-page-render-summary` is built to catch,
|
||||
but the shortform currently can't fix it without discovering a full-form attrs path.
|
||||
3. **(Medium)** Fix the `http://` vs `https://` scheme mismatch between `preview_url` and `edit_url` in
|
||||
write-tool responses (see Bugs).
|
||||
4. **(Medium)** When a shortform field name collides with a node-schema key (`type` on contact-field
|
||||
items, `label` on social-media-follow-network items — both already called out in prose in the
|
||||
`divi-shortform` guide), have the resulting `validation_failed` error say so directly instead of
|
||||
surfacing as "unknown module divi/<field-value>". The current message is technically accurate but
|
||||
doesn't point back to the documented workaround.
|
||||
5. **(Low)** Let `lit/menu-create`'s response include the new menu's initial `menu_rev`, so a
|
||||
first-time `lit/menu-set-items` doesn't need a preceding `lit/menu-get` purely to fetch it.
|
||||
6. **(Low)** Show a one-line example of `lit-divi-catalog`'s result shape (module list vs. single-module
|
||||
detail) in its tool description or the `divi-attrs` guide, consistent with how other tools' guides
|
||||
already embed inline JSON examples.
|
||||
7. **(Nice-to-have)** Consider a `lit-guide` mode that returns all topics concatenated in one call for
|
||||
first-connection priming, alongside the existing per-topic calls (which remain useful for targeted
|
||||
lookups later in a session).
|
||||
|
||||
## Bugs (with repro)
|
||||
|
||||
**Bug: `preview_url` scheme mismatch (`http://` vs. the site's actual `https://`).**
|
||||
- Repro: on this DDEV site (`https://wordpress.ddev.site:8443`, HTTPS-only), call any tool that returns
|
||||
both URLs for a draft, e.g. `lit-content-create` for a page, or any `lit-divi-*` write. Example
|
||||
(Kontakt page, ID 831): response includes
|
||||
`"preview_url":"http://wordpress.ddev.site:8443/?page_id=831&preview=true"` while the same response's
|
||||
`"edit_url":"https://wordpress.ddev.site:8443/wp-admin/post.php?post=831&action=edit"` is correctly
|
||||
`https`.
|
||||
- Impact: a client that opens `preview_url` as given may hit a mixed-content warning, an unwanted
|
||||
http→https redirect, or fail outright depending on the site's HSTS/redirect configuration.
|
||||
|
||||
## Divi shortform expressiveness verdict
|
||||
|
||||
For a typical small-business homepage (hero, services grid, testimonials/values, contact form,
|
||||
CTA, blog teaser text) the shortform covered essentially everything needed — `section`/`row`/`column`
|
||||
nesting, `heading`, `text`, `button`, `image`, `blurb`, `cta`, `icon-list`, `contact-form`, placeholders
|
||||
for the design system's colors — with only two genuine full-form fallbacks in this whole build: the
|
||||
contact-form title heading level (§5 above), and (not really a fallback, just a two-step shortform
|
||||
sequence) setting a contact-field's `type` after insert instead of at insert time. Column-width
|
||||
changes, presets, and `$variable` placeholders were never needed for this content but are documented
|
||||
clearly enough that I'd expect them to work the same way.
|
||||
|
||||
## Cleanup performed
|
||||
|
||||
- All 36 throwaway `lit-mcp-integration-test` application passwords created by repeated `connect()`
|
||||
calls during this trial were removed via `cleanup_app_passwords_for` (verified count 36 → 0).
|
||||
- `.superpowers/trial/` (the scratch scripts used to drive this session) was deleted from the container.
|
||||
- No plugin source code was modified. No existing content (the 4 pre-existing pages, 1 existing post) was
|
||||
touched. Nothing was published.
|
||||
Reference in New Issue
Block a user