{"$schema":"https://wellknown.network/schemas/agent-record-v1.json","schemaVersion":"1","id":"ag_txmnspgpa6ky","handle":"dotsy","url":"https://wellknown.network/agents/dotsy","links":{"self":"https://wellknown.network/agents/dotsy/record.json","html":"https://wellknown.network/agents/dotsy","markdown":"https://wellknown.network/agents/dotsy/record.md","api":"https://wellknown.network/api/v1/agents/dotsy","status":"https://wellknown.network/api/v1/agents/dotsy/status","claim":"https://wellknown.network/agents/dotsy/claim","claimApi":"https://wellknown.network/api/v1/claims","claimDescriptor":"https://wellknown.network/agents/dotsy/claim.json","badge":"https://wellknown.network/agents/dotsy/badge.svg","openapi":"https://wellknown.network/openapi.json","history":"https://wellknown.network/api/v1/agents/dotsy/history","tools":"https://wellknown.network/api/v1/agents/dotsy/tools"},"ard":{"identifier":"urn:air:mcp.dotsy.ai:server:dotsy","type":"application/mcp-server-card+json"},"kind":"mcp_server","declared":{"name":"Dotsy","summary":"Build and publish static sites on Dotsy with draft-first MCP tools, OAuth Connect, and API keys.","description":"Build and publish static sites on Dotsy with draft-first MCP tools, OAuth Connect, and API keys.","publisher":{"name":"ai.dotsy","url":null},"homepage":"https://dotsy.ai","repository":null,"version":"0.4.1","license":null,"protocols":["mcp"],"tags":[],"pricing":null,"endpoints":[{"url":"https://mcp.dotsy.ai/mcp","type":"mcp_streamable_http","auth":null,"probeable":true}],"skills":null,"tools":null,"extra":{"updatedAt":"2026-10-12T17:45:39.740996Z","publishedAt":"2026-10-12T17:45:39.740996Z","registryName":"ai.dotsy/mcp"},"attribution":{"kind":"mcp_registry","name":"mcp_registry","summary":"mcp_registry","version":"mcp_registry","description":"mcp_registry","homepageUrl":"mcp_registry","publisherName":"mcp_registry"}},"derived":{"capabilities":[{"slug":"content.writing","name":"Writing & Editing","confidence":1,"provenance":"derived"},{"slug":"geo.maps","name":"Maps & Places","confidence":0.992,"provenance":"derived"},{"slug":"security.identity","name":"Identity & Access","confidence":0.859,"provenance":"derived"},{"slug":"documents.conversion","name":"Document Conversion","confidence":0.554,"provenance":"derived"},{"slug":"infra.cloud","name":"Cloud Platforms","confidence":0.554,"provenance":"derived"},{"slug":"commerce.pricing","name":"Pricing & Quotes","confidence":0.525,"provenance":"derived"},{"slug":"ai.prompting","name":"Prompt Management","confidence":0.525,"provenance":"derived"},{"slug":"data.web-scraping","name":"Web Scraping","confidence":0.51,"provenance":"derived"}],"categories":["ai","commerce","content","data","documents","geo","infra","security"],"language":"en"},"observed":{"status":"live","statusReason":"Responded 3h ago.","lastOkAt":"2026-10-20T21:27:35.423Z","lastProbedAt":"2026-10-20T21:27:35.423Z","statusComputedAt":"2026-10-20T21:30:19.954Z","reliability30d":{"probes":45,"successRate":1,"p50Ms":47,"basis":"service","measures":{"availability":"availability","latency":"response time","tools":"tool surface observed","summary":"Checks reached the service itself."},"checks":{"total":45,"ok":45,"authBoundaryOk":0,"serviceOk":45,"note":"Counted from the observation rows for the window, checks of the server only (HTTP, A2A card, MCP initialize). ok = authBoundaryOk + serviceOk. `probes` is the sum of daily rollups and includes registry checks, so it can differ from `total`."}},"latestObservations":[{"at":"2026-10-20T21:27:35.423Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":44,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-20T15:31:50.110Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":89,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-20T09:26:38.185Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":91,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-20T03:29:27.788Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":32,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-19T20:31:10.365Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":55,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-19T13:28:21.270Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":67,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-19T07:28:13.758Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":47,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-19T00:32:53.912Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":30,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-18T17:29:32.585Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":104,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}},{"at":"2026-10-18T10:25:12.559Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":66,"error":null,"detail":{"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"toolCount":91,"toolsHash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","serverName":"paige","capabilities":["tools","resources"],"serverVersion":"0.3.0","protocolVersion":"2025-06-18"}}],"tools":[{"name":"create_site","description":"Create empty site; seeds brief. Globally taken slugs auto-suffix to `{slug}-{adjective}-{noun}` (default); pass onSlugTaken:\"fail\" to get a 409 instead of a ren"},{"name":"get_site","description":"Site detail, stats, publicUrl, brief head (default-on), and readiness flags. pages[] is a ~25-entry head by default (stats.pages has the true count) — pass page"},{"name":"update_brief","description":"Update `_intent/site.md` (site brief) or a page brief when `page` is set. See field descriptions for rules, notes, and head enums."},{"name":"get_analytics","description":"Read-only traffic overview (default) or full breakdowns via include[]; optional bot id for crawler page drill-down."},{"name":"update_site","description":"Update site settings: title, keyConvention, or editingMode ('live' = each editor save publishes immediately; 'staged' = saves stay drafts until publish_site). S"},{"name":"rename_site","description":"Rename the site slug (public hostname {slug}.dotsy.site). Owner-only. After success: use the returned slug in every later tool. Published site: the old slug red"},{"name":"publish_site","description":"Publish draft to live. feedback and handoff required (use dummy feedback + one-line handoff if clean)."},{"name":"get_draft_status","description":"Pending draft changes: content keys and routes with status. May include requiredDecision (publish_confirm) before publish_site."},{"name":"discard_drafts","description":"Discard unpublished drafts: all (omit key/keys/routes), one content key (key), or a selection (keys, routes). Destructive — check get_draft_status first."},{"name":"delete_site","description":"Permanently delete site; confirmSlug must equal slug."},{"name":"bulk_put_content","description":"Stage many content keys (pages, layouts, fragments). Content only — not routes or assets. Each body ≤60KB and the whole call ≤80KB; larger pages use stage_conte"},{"name":"get_site_manifest","description":"Content keys + hashes only (optional routes=true) — static files live in list_assets, not manifest. Diff keys+hashes before apply_changeset; stage only changed "},{"name":"find_replace","description":"Case-insensitive find→replace across pages in one staged commit. Batch all replacements into ONE call — never parallelize find_replace on the same key (concurre"},{"name":"search_content","description":"Find pages containing a phrase (grep without reading bodies). Requires do-sqlite — graduate_site first if search_unavailable."},{"name":"graduate_site","description":"Enable site search & collections (kv → do-sqlite), one-way. Unlocks search_content, collection indexes, visitor /__paige/search, and moves any R2 form-submissio"},{"name":"apply_changeset","description":"Stage writes[] + deletes[] in one call; attach routes via writes[].route. writes[].ifMatch (hash from get_site_manifest; \"\" = must not exist) guards each key (a"},{"name":"import_site_from_url","description":"Import URL to draft. On complete, response may include requiredDecision — preview then ask mirror vs editable before structure_*."},{"name":"get_structure_status","description":"Flat vs structured page counts; informs user — do not auto-run analyze/apply while requiredDecision pending."},{"name":"structure_site_analyze","description":"Propose shared layout + fragments; persist:false first. Wait for mirror-vs-editable answer if requiredDecision pending."},{"name":"decompose_analyze","description":"Deprecated — use structure_site_analyze."},{"name":"structure_site_apply","description":"Rewrite flat pages to layout + fragments (draft). Do not call while requiredDecision pending — user must pick editable first. It extracts header/footer only: af"},{"name":"get_structure_result","description":"Last structure apply job result (structuredKeys, conflicts, partial)."},{"name":"adapt_site_analyze","description":"Propose nav wiring + blog collectionization for an imported mirror (draft, opt-in). Read-only; returns per-adapter reports + proposalEtag. Nav decline may inclu"},{"name":"adapt_site_apply","description":"Apply the adapt proposal (draft): wire nav into the menu store and/or rewrite the blog index to a live <dotsy-collection>. One undo covers the whole apply; a se"},{"name":"adapt_site_revert","description":"Atomically restore everything the last adapt_site_apply wrote (fragments, the menu doc when the op touched it, index, member configs, routes)."},{"name":"get_adapt_status","description":"Read adapt state: proposal summary + proposalEtag (+ applied/consumed flag), undo-record presence, active or queued job progress. Use to resume in a fresh sessi"},{"name":"list_content","description":"List content keys (HTML/md); not assets — use list_assets. Defaults to 50 keys per page — raise limit (max 1000) or pass cursor (from nextCursor) to page throug"},{"name":"get_content","description":"Read one content key; optional selector (region) or published=true (live body). Bodies over 40KB without a selector return a truncated summary — use selector, b"},{"name":"put_content","description":"Stage one content key. Body ≤60KB. Larger pages: stage_content_chunk + apply_changeset bodyStaging, dotsy push, or PUT content-raw. Edits to an existing large p"},{"name":"stage_content_chunk","description":"Append one ≤60KB chunk of a large new page. Omit stagingId on seq 0; pass the returned stagingId on later seq. Set finalize:true on the last chunk, then apply_c"},{"name":"patch_content","description":"Edit one content key via ops[] (selector ops or replace-string; wrap/unwrap/move restructure siblings). Stages draft; op shapes in schema."},{"name":"batch_patch_content","description":"patch_content on multiple keys; all-or-nothing validation. Preferred edit path for pages over 60KB. replace-string value/replace ≤60KB; whole call ≤80KB."},{"name":"delete_content","description":"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"},{"name":"rename_content","description":"Re-key a page by slug (draft or published static). Server-side hash copy — no body round-trip. Default deletes static routes (obscure-URL); movePath:true repoin"},{"name":"put_post","description":"Stage a blog post via structured meta+body (?format=post). Key e.g. blog/my-post.html. Declared membership: meta.collections; schedule: meta.visibleAt; SEO titl"},{"name":"inspect_collection","description":"Collection state — count, server.mode, sample entries, tag (membershipKind, match=) when discovered; plus name, noun, URL pattern, layout, where it's listed, an"},{"name":"list_collections","description":"Collections on the site — name, noun, item count (or 'not published yet'), URL pattern, and whether any member is staged. Includes a collection only a draft pag"},{"name":"update_collection","description":"Rename a collection's display name and/or noun (e.g. the generic 'posts' key shown as 'Recipes' / 'recipe') — cosmetic only, stored separately from the tag that"},{"name":"new_post_template","description":"Read-only scaffold for a new post — suggested key/path, membership meta, blank or duplicate body. Then put_post (+ optional put_route)."},{"name":"reindex_collection","description":"Build/finish collection indexes after publish returns collectionDataPending or collectionSqlPending."},{"name":"preview_page","description":"Verify-only composed HTML check; home preview may include requiredDecision — ask mirror vs editable before structure_*."},{"name":"verify_page","description":"Pre-publish lint (tags, assets, layout). Pair with preview_page — see get_skill('verify')."},{"name":"preflight_site","description":"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."},{"name":"start_edit_session","description":"Open a draft session. Returns preview/edit URLs for the person (see linkGuidance). draftEditorUrl only when the site gives AI agents Full access. path defaults "},{"name":"get_site_outline","description":"Site shape: every layout and shared section (`sections`: fragments/* by name, with page count, whether the file exists and staged-change flag), whether a layout"},{"name":"share_section","description":"Make a block that repeats on several pages one shared section, or change where one shows. No arguments: lists blocks repeated on 3+ pages. sourceKey + selector "},{"name":"detach_section","description":"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 "},{"name":"get_route","description":"Get one route by path (home is '/')."},{"name":"put_route","description":"Create/replace route; contentKey derived from path if omitted. On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQuery), sending nul"},{"name":"patch_route","description":"Partial route update (only sent fields change; an OMITTED field is left alone). On a field that accepts null (contentKey, redirectTo, redirectStatus, preserveQu"},{"name":"rename_page","description":"Change a page's URL: moves its route from `from` to `to`. Menu links and other redirects that pointed at the old path retarget automatically; a 301 is left at t"},{"name":"delete_route","description":"Remove a route by path."},{"name":"list_goals","description":"List site conversion goals (pageview paths and form submits). Required before start_test."},{"name":"put_goals","description":"Create or replace all site conversion goals (≤50). Required before start_test when no goal exists."},{"name":"estimate_test","description":"Estimate days to reach promote readiness for a page (traffic baseline). Read-only."},{"name":"start_test","description":"Start a same-URL A/B test (draft or live source). Rules → get_skill('verify') § A/B."},{"name":"promote_experiment","description":"Promote a winning A/B variant to live — explicit user instruction only; see get_skill('verify') § A/B."},{"name":"delete_draft_test","description":"Remove a draft A/B test (releases variant bodies; does not publish)."},{"name":"set_experiment_note","description":"Record what was learned from an ended A/B test (≤500 chars; null clears)."},{"name":"get_menu","description":"Read a menu's draft items and etag; menu rules → get_skill('nav')."},{"name":"put_menu_item","description":"Partial upsert one menu item: provided fields overwrite; omitted html/childTemplate/childShell/attrs/data are preserved. To remove those keys, use put_menu_doc."},{"name":"delete_menu_item","description":"Remove one item from a menu (page stays live)."},{"name":"reorder_menu","description":"Set final menu order. Pass ifMatch from get_menu; stale → 409 with current items."},{"name":"get_menu_doc","description":"Read the whole menu doc (all menus + shell/template), etag for put_menu_doc, and issues (by item id) — page_not_routable, page_missing, page_dead_link, page_is_"},{"name":"put_menu_doc","description":"Replace the whole menu doc (ifMatch from get_menu_doc required). Each item may omit menu — the enclosing menu key in doc is used."},{"name":"list_assets","description":"CMS uploads under assets/ only — not mirrored clone CDN (get_site mirroredAssets or import warmJob). Not content — use list_content. Uploaded files are live in "},{"name":"get_asset","description":"Read one asset (metadata + base64 + stored sha256). sha256 is the stored blob; may differ from upload bodySha256 when IMAGES re-encodes rasters. Uploaded files "},{"name":"bulk_put_assets","description":"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"},{"name":"delete_asset","description":"Delete a static asset by key. The editor and draft preview drop it at once; the published site drops it at the next publish."},{"name":"start_site","description":"Start one draft site with no Dotsy account. Returns slug + guestToken — pass guestToken on every later tool call until claim_site."},{"name":"claim_site","description":"Move the guest draft onto your signed-in Dotsy account. Requires OAuth Connect; pass the guestToken from start_site."},{"name":"list_skills","description":"Paige build skills (id + whenToUse). Pull get_skill on demand — do not duplicate in tool text."},{"name":"get_skill","description":"Skill body + optional questions[]. Accepts an id OR a synonym (e.g. 'list', 'menu') — resolves aliases and suggests near-misses. Start with intake when ambiguou"},{"name":"submit_feedback","description":"Optional mid-session feedback (required feedback rides with publish_site)."},{"name":"get_activity","description":"List site activity or fetch one change diff (pass id). Filters: who, state, actor (me), since, limit≤20. Notes from context are visible to everyone on the site "},{"name":"revert_change","description":"Undo one activity event by staging the prior version as draft only — call publish_site to go live. Respects AI access."},{"name":"get_account_context","description":"Account, user, memberships[]. Pass workspace on writes when memberships.length > 1."},{"name":"add_custom_domain","description":"Connect apex and/or www on a paid plan; starts a DNS connect session."},{"name":"get_custom_domain_connect","description":"Current connect session and nextAction (browser URL or manual DNS guide)."},{"name":"reapply_custom_domain_oauth","description":"Re-apply DNS with stored Cloudflare OAuth grant (no browser)."},{"name":"refresh_custom_domain","description":"Poll one hostname until DNS/cert propagates."},{"name":"wait_for_custom_domain","description":"Poll one hostname until it is connected or the timeout is reached."},{"name":"set_primary_custom_domain","description":"Set the canonical host (301 target) when multiple hostnames are connected."},{"name":"list_custom_domains","description":"List all custom hostnames and connection status for the site."},{"name":"remove_custom_domain","description":"Remove one custom hostname from the site."},{"name":"list_form_submissions","description":"List form submissions (metadata + bounded field preview — not full PII). Works on kv and do-sqlite backends."},{"name":"get_form_submission","description":"Read one form submission with the full field payload (explicit PII access — use only when needed)."},{"name":"list_my_sites","description":"List your Dotsy sites. Renders a site-picker for humans; agent reads structuredContent. Use to choose a site — not for editing content (use edit_page) or verify"},{"name":"list_pages","description":"List pages on a site — route settings (path/title/navLabel/purpose) by default, one row per route. Renders a page-picker for humans (redirect and worker routes "},{"name":"edit_page","description":"Open the interactive page canvas to change content: the user clicks an element, then you call patch_content. Use when the intent is editing — not for verify-onl"},{"name":"get_site_build_progress","description":"Read-only build progress for humans (build-theater view) during structure/adapt or after import. Agent reads structuredContent.forHumans — optional; do not call"}],"package":null,"toolSurface":{"id":"ts_a75gkv7ckjan","endpointId":"ep_trjmd65s3t4f","hash":"fd5bc285d5116be24a133d9c915ded411aa861ff51f5764f61aea6c1c5ca79f3","toolCount":91,"serverName":"paige","serverVersion":"0.3.0","protocolVersion":"2025-06-18","firstSeenAt":"2026-10-18T10:25:12.572Z","lastSeenAt":"2026-10-20T21:27:35.428Z","observations":10,"toolNames":["create_site","get_site","update_brief","get_analytics","update_site","rename_site","publish_site","get_draft_status","discard_drafts","delete_site","bulk_put_content","get_site_manifest","find_replace","search_content","graduate_site","apply_changeset","import_site_from_url","get_structure_status","structure_site_analyze","decompose_analyze","structure_site_apply","get_structure_result","adapt_site_analyze","adapt_site_apply","adapt_site_revert","get_adapt_status","list_content","get_content","put_content","stage_content_chunk","patch_content","batch_patch_content","delete_content","rename_content","put_post","inspect_collection","list_collections","update_collection","new_post_template","reindex_collection","preview_page","verify_page","preflight_site","start_edit_session","get_site_outline","share_section","detach_section","get_route","put_route","patch_route","rename_page","delete_route","list_goals","put_goals","estimate_test","start_test","promote_experiment","delete_draft_test","set_experiment_note","get_menu","put_menu_item","delete_menu_item","reorder_menu","get_menu_doc","put_menu_doc","list_assets","get_asset","bulk_put_assets","delete_asset","start_site","claim_site","list_skills","get_skill","submit_feedback","get_activity","revert_change","get_account_context","add_custom_domain","get_custom_domain_connect","reapply_custom_domain_oauth","refresh_custom_domain","wait_for_custom_domain","set_primary_custom_domain","list_custom_domains","remove_custom_domain","list_form_submissions","get_form_submission","list_my_sites","list_pages","edit_page","get_site_build_progress"],"distinctSurfaces":7},"endpointFacts":[{"id":"ep_trjmd65s3t4f","url":"https://mcp.dotsy.ai/mcp","type":"mcp_streamable_http","factsCheckedAt":"2026-10-18T10:25:12.623Z","auth":{"observedAt":"2026-10-18T10:25:12.641Z","authRequired":false,"scheme":null,"resourceMetadata":null,"authorizationServer":null,"conformance":{"dpop":false,"rfc8414":false,"rfc9728":false,"pkceS256":false,"clientIdMetadataDocument":false,"dynamicClientRegistration":false}},"tls":{"observedAt":"2026-10-18T10:25:12.657Z","protocol":"TLSv1.3","chainValid":true,"chainError":null,"hostMatches":true,"subject":"dotsy.ai","issuer":{"commonName":"WE1","organization":"Google Trust Services"},"validFrom":"2026-10-05T14:15:24.000Z","validTo":"2027-01-03T15:14:05.000Z","daysToExpiry":74,"sanCount":2,"fingerprint256":"BE:6B:77:66:79:2A:48:9E:B4:B9:64:EF:7F:21:29:57:BE:59:89:89:4B:48:AA:F6:27:13:6D:0E:6A:22:CF:DB"}}]},"verification":{"claimed":false,"claimedAt":null,"proofs":[]},"provenance":{"sources":[{"source":"mcp_registry","key":"ai.dotsy/mcp","url":"https://registry.modelcontextprotocol.io/v0/servers/ai.dotsy%2Fmcp","firstSeenAt":"2026-10-08T22:20:31.626Z","fetchedAt":"2026-10-17T19:22:07.739Z","normalizedAt":"2026-10-17T19:22:07.739Z"}]},"firstSeenAt":"2026-10-08T22:20:31.626Z","updatedAt":"2026-10-20T21:32:09.700Z"}