Every change Wellknown observed on this MCP server, newest first, with what it was before and what it became. Tool-surface changes carry the definition diff. Nothing here is edited after the fact.
Changed the definition of "apply_changeset"
AtomicStage writes[] + deletes[];in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; "" = must not exist) guards each key (a delete takes { key, ifMatch } too); atomic:true makes one stale key write nothing (409 changeset_conflict). At most 10 writes; each body ≤60KB and ⟨16 unchanged words⟩
{"$schema":"http://json-schema.org/draft-07/schema#","additionalProperties":false,"properties":{"atomic":{"description":"All or nothing: every writes[].ifMatch and deletes[].ifMatch is checked before anything is written. One stale key writes nothing and the call fails with 409 changeset_conflict listing `conflicts`. Default: a stale write is skipped and listed, the rest apply.","type":"boolean"},"context":{"description":"One sentence: why ⟨37 unchanged words⟩ "},"deletes":{"default":[],"description":"Keys to delete: a key, or { key, ifMatch } (the hash you read) so a page changed since is not deleted (listed in conflicts).","items":{"anyOf":[{"minLength":1,"type":"string"},{"additionalProperties":false,"properties":{"ifMatch":{"minLength":1,"type":"string"},"key":{"minLength":1,"type":"string"}},"required":["key","ifMatch"],"type":"object"}]},"maxItems":50,"type":"array"} ⟨724 unchanged words⟩
Changed the definition of "bulk_put_assets" and "update_site"
⟨39 unchanged words⟩ (JS/CSS match; WebP rasters may differ). JPEG/PNG/static GIF are re-encoded to WebP (response key photo.webp; /assets/photo.jpg keeps serving it) — use the response key in CSS/JS; optimize:false stores the bytes as sent at your key; animated GIFs are always kept. Uploaded files are live in the editor and ⟨14 unchanged words⟩
⟨191 unchanged words⟩ ","minLength":1,"type":"string"},"optimize":{"description":"Default true: a JPEG, PNG or static GIF is re-encoded to WebP (key photo.jpg is stored as photo.webp, and /assets/photo.jpg still serves it). false stores the bytes exactly as sent, at the key given. Animated GIFs are always kept as sent.","type":"boolean"}},"required":["key"],"type": ⟨21 unchanged words⟩
Changed the definition of "batch_patch_content" and "patch_content"
⟨86 unchanged words⟩ items":{"additionalProperties":false,"properties":{"anchored":{"type":"boolean"},"attrs":{"additionalProperties":{"type":"string" ⟨109 unchanged words⟩
⟨79 unchanged words⟩ items":{"additionalProperties":false,"properties":{"anchored":{"type":"boolean"},"attrs":{"additionalProperties":{"type":"string" ⟨97 unchanged words⟩
Changed the definition of "batch_patch_content", "patch_content" and "share_section"
⟨86 unchanged words⟩ items":{"additionalProperties":false,"properties":{"attrs":{"additionalProperties":{"type":"string"},"type":"object"},"expect":{"minimum":0,"type":"integer ⟨16 unchanged words⟩ ,"insert-after","replace-inner-html","replace-outer-html","replace-string","wrap","unwrap","move"],"type":"string"},"position":{"enum":["before","after"],"type":"string"},"replace":{"type":"string"},"selector":{"minLength":1,"type":"string"},"selectors":{"items":{"minLength":1,"type":"string"},"maxItems":50,"minItems":1,"type":"array"},"tag":{"enum":["div","section","footer","header","article","aside"],"type":"string"},"target":{"minLength":1,"type":"string" ⟨36 unchanged words⟩
Changed the definition of "bulk_put_assets", "preflight_site", "put_menu_doc" and 1 more
Upload 1–200 CMS assets; key = path after /assets/. Each item: exactly one of bodyBase64, bodyText (plain text for .css/.js/.mjs/.json/.svg/.txt/.xml only — not .html pages), or importFromUrl. Optional bodySha256 verifies submitted bytes before write; response ⟨33 unchanged words⟩
⟨124 unchanged words⟩ {64}$","type":"string"},"bodyText":{"type":"string"},"contentType":{"minLength":1,"type":"string ⟨83 unchanged words⟩
Certificate recorded, valid to 2027-01-03
Authorization not required
Added "detach_section" and "share_section"; changed the definition of "apply_changeset", "delete_content", "get_site_outline" and 6 more (89 tools before, 91 now)
⟨455 unchanged words⟩ type":"null"}]},"layout":{"description":"Layout NAME, bare: \"default\" for layouts/default.html — not the file key. It must already exist as layouts/<name>.html (400 layout_not_found otherwise). null removes the layout.","type":["string","null"]},"nav ⟨294 unchanged words⟩
Delete content key (tombstone if published). A fragments/* section that pages still place is refused with section_in_use (lists the pages) — detach it or remove it first.Unknown → Live
First tool surface recorded: 89 tools (server version 0.3.0)
https://mcp.dotsy.ai/mcp (mcp_streamable_http) — from mcp_registry, with the record
Showing the latest 11 events. The API returns up to 500 and filters by kind: ?kind=tool_surface_changed
⟨56 unchanged words⟩ reveal: pill|seal}); it is live immediately. trailingSlash (ignore|never|always) sets the page URL form: the other form 301s to it and canonicals/sitemap use it; live immediately. ownSeoFiles: true lets the next publish use the site's own robots.txt that blocks the whole site (Disallow: /) and its own static sitemap.xml (replaces the generated one, collection pages included); a robots.txt without Disallow: / is used anyway.
⟨136 unchanged words⟩ ,"directory"],"type":"string"},"ownSeoFiles":{"description":"Use the site's own robots.txt and sitemap.xml even when they can hurt search: a robots.txt that blocks the whole site (Disallow: /), and a static sitemap.xml, which replaces the generated one (collection pages included). Off by default; a robots.txt without Disallow: / is always used. Takes effect at the next publish.","type":"boolean"},"signature":{"additionalProperties":false,"description":"\ ⟨78 unchanged words⟩ :"string"},"title":{"maxLength":200,"type":"string"},"trailingSlash":{"description":"Page URL form. 'ignore' (default): /blog and /blog/ both serve the page. 'never': /blog/ 301s to /blog. 'always': /blog 301s to /blog/. The canonical tag and sitemap use the same form. Live immediately, no publish needed.","enum":["ignore","never","always"],"type":"string"}},"required":[ ⟨4 unchanged words⟩
Edit one content key via ops[] (selector ops orreplace-stringreplace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema.
⟨79 unchanged words⟩ items":{"additionalProperties":false,"properties":{"attrs":{"additionalProperties":{"type":"string"},"type":"object"},"expect":{"minimum":0,"type":"integer ⟨16 unchanged words⟩ ,"insert-after","replace-inner-html","replace-outer-html","replace-string","wrap","unwrap","move"],"type":"string"},"position":{"enum":["before","after"],"type":"string"},"replace":{"type":"string"},"selector":{"minLength":1,"type":"string"},"selectors":{"items":{"minLength":1,"type":"string"},"maxItems":50,"minItems":1,"type":"array"},"tag":{"enum":["div","section","footer","header","article","aside"],"type":"string"},"target":{"minLength":1,"type":"string" ⟨24 unchanged words⟩
Make a block that repeats on several pages one shared section(a fragment), or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector (or selectors: a sibling run, one wrapper): a dry run finds every page with it; dryRun:false shares it. name + scope/hide/show: move an existing sectionbetween its pages andto/from every page, or hide/show it per page. ⟨46 unchanged words⟩ ) until all are moved. Always a draft—(publish_site to go live; undowith:revert_change.revert_change).
⟨151 unchanged words⟩ "minLength":1,"type":"string"},"keepLayout":{"description":"With selectors: wrapper gets style=\"display:contents\" so a flex/grid parent lays the run out as before. Default true.","type":"boolean"},"keys":{"description":"Pages with an exact ⟨122 unchanged words⟩ "minLength":1,"type":"string"},"selectors":{"description":"Instead of selector: a run of elements next to each other in one parent, in page order (each matches one element). Shared as one section inside a wrapper, on the pages with the same run.","items":{"maxLength":2000,"minLength":1,"type":"string"},"maxItems":50,"minItems":1,"type":"array"},"show":{"description":"Pages that should show ⟨61 unchanged words⟩ is on, e.g. 'about.html'.","minLength":1,"type":"string"},"wrapTag":{"description":"With selectors: the wrapper. Default footer when the run ends the page content, else section.","enum":["div","section","footer","header","article","aside"],"type":"string"}},"required":[ ⟨4 unchanged words⟩
Plan check before publish or large build (counts, route soft limit, seo suggestions). When ok is false, blocking lists the failed suggestion codes. Read-only.
Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used.⟨122 unchanged words⟩ ,"label","kind","ref","id","menu"],"type":"object"},"type":" ⟨70 unchanged words⟩
⟨142 unchanged words⟩ : must|nice|skip), editingModel (ai|simple-editor|creator), sections,design.{design (strict): palette string[] max 8, type string[] max 6, layout}.string max 80 chars — no other keys. Page: purpose, design.{direction,hero}."} ⟨422 unchanged words⟩
Site shape: every layout and shared sectionwith(`sections`:itsfragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout is missing, redirects count ⟨11 unchanged words⟩ placements. Use for 'is my layout missing','what'whichdoessharedthissectionssectionexistreach'and where', 'where does this menu show' — without ⟨16 unchanged words⟩
⟨319 unchanged words⟩ ,"type":"string"},"layout":{"description":"Layout NAME, bare: \"default\" for layouts/default.html — not the file key. It must already exist as layouts/<name>.html (400 layout_not_found otherwise). null removes the layout.","type":["string","null"]},"nav ⟨286 unchanged words⟩
⟨89 unchanged words⟩ :{"description":"Assert absent — e.g. `<dotsy-`, `<paige-`for unresolved template tags.","items":{" ⟨50 unchanged words⟩
⟨81 unchanged words⟩ ,"type":"string"},"key":{"description":"Content key: a page (about.html, blog/post.html), a layout (layouts/<name>.html) or a fragment (fragments/<name>.html). A <dotsy-fragment name> tag and a route's layout use the bare name, not the key.","type":"string"},"site":{"description" ⟨13 unchanged words⟩
⟨319 unchanged words⟩ ,"type":"string"},"layout":{"description":"Layout NAME, bare: \"default\" for layouts/default.html — not the file key. It must already exist as layouts/<name>.html (400 layout_not_found otherwise). null removes the layout.","type":["string","null"]},"nav ⟨286 unchanged words⟩
⟨74 unchanged words⟩ everything back, so resend the same call. On fragments/<name>.html it also rewrites every <dotsy-fragment name> reference and hide marker in the same all-or-nothing commit. Then publish_site.
⟨13 unchanged words⟩ requiredDecision pending — user must pick editable first. It extracts header/footer only: afterwards share_section with no arguments lists the other repeated blocks (banner, CTA).
Give one page its own copy of a shared section; every other page keeps the shared one. A draft — publish_site to go live. A section the layout places is hidden on that page instead (share_section name + hide).
Make a block that repeats on several pages one shared section (a fragment), or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector: a dry run finds every page with it; dryRun:false shares it. name + scope/hide/show: move an existing section between its pages and every page, or hide/show it per page. Over the per-call cap (too_many_pages, nothing written): share with keys, then again under the same name from a page that still has a copy; for every page then move with name + scope every_page + keys (max-1 pages a call; put all hide on the first) until a…