{"protocol_version":"2024-11-05","name":"SiteForge","transports":{"json_rpc":{"url":"https:\/\/fedcanvas.be\/mcp\/rpc","aliases":["https:\/\/fedcanvas.be\/_mcp"]}},"auth_providers":[{"id":"mcp_token_bearer","type":"bearer"}],"tools":[{"name":"read_datagroup","description":"Reads SiteForge data items by id, or filters a datagroup by a field value.","inputSchema":{"type":"object","oneOf":[{"type":"object","required":["item_id"],"additionalProperties":false,"properties":{"item_id":{"type":"integer","minimum":1,"description":"The data_item node id to read. Returns zero or one row."}}},{"type":"object","required":["datagroup_machine_name","filter_key","filter_value"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The owning subsite key. A datagroup is unique per (subsite, machine name); the dispatcher resolves this to the subsite scope."},"datagroup_machine_name":{"type":"string","minLength":1,"description":"The datagroup field_machine_name to read items from, within the subsite scope."},"filter_key":{"type":"string","minLength":1,"description":"The flat-dict field key each row is filtered on (string comparison)."},"filter_value":{"type":"string","description":"The value the filter_key must equal for a row to be returned."},"limit":{"type":"integer","minimum":1,"maximum":100,"description":"Maximum rows to return (default 25, hard cap 100)."}}}]},"outputSchema":{"type":"object","required":["rows"],"properties":{"rows":{"type":"array","description":"The matching data item rows (zero, one, or many).","items":{"type":"object","required":["item_id","datagroup_id","fields"],"properties":{"item_id":{"type":"integer","description":"The data_item node id."},"datagroup_id":{"type":"integer","description":"The owning datagroup node id (0 when the item has no datagroup reference)."},"fields":{"type":"object","description":"The item\u0027s flat-dict, keyed by each datagroup slot key. Repeatable slots yield arrays; single slots yield the scalar value (null when unset).","additionalProperties":true}}}}}},"path":"\/tools\/read_datagroup","methods":["POST"]},{"name":"test_ping","description":"Test fixture for plugin discovery - returns pong.","inputSchema":{"type":"object","additionalProperties":true},"outputSchema":{"type":"object","required":["pong","echo"],"properties":{"pong":{"type":"boolean"},"echo":{"type":"object"}}},"path":"\/tools\/test_ping","methods":["POST"]},{"name":"generate_theme_design_from_prompt","description":"Synthesizes a complete, WCAG-harmonized design token set from a natural language brand description and compiles tokens.css.","inputSchema":{"type":"object","required":["subsite_id","prompt"],"properties":{"subsite_id":{"type":"string","description":"The subsite machine name or entity ID."},"prompt":{"type":"string","description":"Natural language brand\/theme description (e.g. \u0027Warm terracotta organic bakery with rounded cards\u0027)."},"dark_mode":{"type":"boolean","description":"Whether to auto-derive and enable a high-contrast dark mode palette.","default":true},"font_provider":{"type":"string","description":"Font provider: \u0027auto\u0027, \u0027fontshare\u0027, \u0027google\u0027, \u0027self_hosted\u0027, \u0027system\u0027.","enum":["auto","fontshare","google","self_hosted","system"],"default":"auto"}},"additionalProperties":false},"path":"\/tools\/generate_theme_design_from_prompt","methods":["POST"]},{"name":"report_environment","description":"Declare the client execution environment; the server acknowledges and confirms the floor tier is always available.","inputSchema":{"type":"object","required":["exec_sandbox"],"properties":{"exec_sandbox":{"type":"boolean","description":"Whether the client can execute a vendored detector in a sandbox."},"runtime":{"type":"object","description":"Optional opaque runtime descriptor for future tier negotiation."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["acknowledged","floor_available","tier","probe"],"properties":{"acknowledged":{"type":"boolean"},"floor_available":{"type":"boolean"},"tier":{"type":"string"},"probe":{"type":"string"}},"additionalProperties":false},"path":"\/tools\/report_environment","methods":["POST"]},{"name":"set_design_context","description":"Stores a subsite\u0027s PRODUCT.md or DESIGN.md design context markdown, and\/or its WCAG conformance target (wcag_target: \u00272.1 AA\u0027 | \u00272.2 AA\u0027 | \u00272.2 AAA\u0027 \u2014 an OWNER decision: ask your user, never pick silently).","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"name":{"type":"string","enum":["PRODUCT.md","DESIGN.md"],"description":"The design context document to write."},"markdown":{"type":"string","description":"The markdown body to store for the document."},"wcag_target":{"type":"string","enum":["2.1 AA","2.2 AA","2.2 AAA"],"description":"The site\u0027s WCAG conformance target \u2014 an OWNER decision (legal\/compliance context): ask your user which standard the site must meet; never pick silently. Can be stored alone or together with a document."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected subsite id."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/set_design_context","methods":["POST"]},{"name":"get_theme_design_tokens","description":"Returns active visual design tokens, matched preset, and WCAG contrast metrics for a subsite.","inputSchema":{"title":"get_theme_design_tokens","description":"Returns active visual design tokens, matched preset, and WCAG contrast metrics for a subsite.","type":"object","properties":{"sitekey":{"type":"string","description":"The subsite machine name (e.g. acme-corp)."}},"required":["sitekey"],"additionalProperties":false},"path":"\/tools\/get_theme_design_tokens","methods":["POST"]},{"name":"get_design_context","description":"Returns a subsite\u0027s stored PRODUCT.md or DESIGN.md design context markdown for a subsite the caller is a member of.","inputSchema":{"type":"object","required":["sitekey","name"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"name":{"type":"string","enum":["PRODUCT.md","DESIGN.md"],"description":"The design context document to read."}}},"outputSchema":{"type":"object","required":["name","markdown"],"additionalProperties":false,"properties":{"name":{"type":"string","enum":["PRODUCT.md","DESIGN.md"],"description":"The design context document that was read."},"markdown":{"type":["string","null"],"description":"The stored markdown body, or null when the document is not set."},"note":{"type":"string","description":"A human-readable note present only when the document is not set."},"design_interview":{"type":"object","description":"Present ONLY when the requested document is unset: the canonical design interview. Relay its questions to your USER verbatim and store the answers via set_design_context.","properties":{"required":{"type":"boolean"},"protocol":{"type":"string"},"questions":{"type":"array","items":{"type":"string"}}}}}},"path":"\/tools\/get_design_context","methods":["POST"]},{"name":"set_theme_design_tokens","description":"Updates visual design tokens for a subsite and recompiles tokens.css.","inputSchema":{"title":"set_theme_design_tokens","description":"Updates visual design tokens for a subsite and recompiles tokens.css.","type":"object","properties":{"sitekey":{"type":"string","description":"The subsite machine name."},"tokens":{"type":"object","description":"Structured design tokens map (color, spacing, typography, radius).","properties":{"color":{"type":"object"},"spacing":{"type":"object"},"typography":{"type":"object"},"radius":{"type":"object"}}}},"required":["sitekey","tokens"],"additionalProperties":false},"path":"\/tools\/set_theme_design_tokens","methods":["POST"]},{"name":"design_guidance","description":"Returns a design discipline instruction block with the subsite\u0027s stored brand brief injected; your agent does the reasoning. Commands: palette (CHOOSE SITE COLORS from the brand brief \u2014 use this before ever touching theme colors), accessible (WCAG AA audit \u2014 alt text, names, labels, contrast), typeset (typography), harmonize (unify to the design tokens), polish (final quality pass), critique (scored design review), distill (simplify).","inputSchema":{"type":"object","required":["command"],"properties":{"command":{"type":"string","description":"The design discipline to apply (e.g. polish, critique, distill, typeset, harmonize)."},"sitekey":{"type":"string","description":"The subsite key whose PRODUCT.md\/DESIGN.md context is injected."},"target":{"type":"string","description":"What to apply the guidance to."}},"additionalProperties":false},"path":"\/tools\/design_guidance","methods":["POST"]},{"name":"import_theme_design_dtcg","description":"Imports W3C DTCG or Figma Token Studio JSON payload into subsite design tokens.","inputSchema":{"title":"import_theme_design_dtcg","description":"Imports W3C DTCG or Figma Token Studio JSON payload into subsite design tokens.","type":"object","properties":{"sitekey":{"type":"string","description":"The subsite machine name."},"payload":{"description":"The DTCG tokens JSON string or parsed JSON object."}},"required":["sitekey","payload"],"additionalProperties":false},"path":"\/tools\/import_theme_design_dtcg","methods":["POST"]},{"name":"ingest_local_findings","description":"Normalise a client-side detector run JSON into the canonical design verdict.","inputSchema":{"type":"object","required":["raw"],"properties":{"raw":{"type":"string","description":"The JSON stdout of a client-side detector run: either an object {\u0022findings\u0022: [...]} or a bare array of findings."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["engine","exitCode","counts","findings"],"properties":{"engine":{"type":"string","enum":["server","local"]},"exitCode":{"type":"integer","description":"0 iff no findings; nonzero otherwise."},"counts":{"type":"object","properties":{"P0":{"type":"integer"},"P1":{"type":"integer"},"P2":{"type":"integer"},"P3":{"type":"integer"}}},"findings":{"type":"array","items":{"type":"object","required":["rule","severity","message"],"properties":{"rule":{"type":"string"},"severity":{"type":"string","enum":["P0","P1","P2","P3"]},"message":{"type":"string"},"file":{"type":["string","null"]},"line":{"type":["integer","null"]},"source":{"type":"string","enum":["server","local"]}}}}}},"path":"\/tools\/ingest_local_findings","methods":["POST"]},{"name":"apply_theme_design_preset","description":"Applies a curated visual design preset to a subsite and recompiles tokens.css.","inputSchema":{"title":"apply_theme_design_preset","description":"Applies a curated visual design preset to a subsite and recompiles tokens.css.","type":"object","properties":{"sitekey":{"type":"string","description":"The subsite machine name."},"preset_id":{"type":"string","description":"Preset machine name (e.g. corporate, minimalist, vibrant, editorial, cyberpunk, forest, sunset, slate)."}},"required":["sitekey","preset_id"],"additionalProperties":false},"path":"\/tools\/apply_theme_design_preset","methods":["POST"]},{"name":"check_design_quality","description":"Run the deterministic design-quality detector over supplied HTML or a subsite built output.","inputSchema":{"type":"object","properties":{"sitekey":{"type":"string","description":"Subsite key; scans that subsite\u0027s most recent built static output. Requires membership. Mutually exclusive with `html`."},"html":{"type":"string","description":"Raw HTML (with inline styles \/ \u003Cstyle\u003E blocks) to scan. Mutually exclusive with `sitekey`."},"file":{"type":"string","description":"Optional path label applied to findings."},"providers":{"type":"array","items":{"type":"string"},"description":"Optional provider id allow-list; default = all applicable enabled providers."}},"oneOf":[{"required":["sitekey"]},{"required":["html"]}]},"outputSchema":{"type":"object","required":["engine","exitCode","counts","findings"],"properties":{"engine":{"type":"string","enum":["server","local"]},"exitCode":{"type":"integer","description":"0 iff no findings; nonzero otherwise."},"counts":{"type":"object","properties":{"P0":{"type":"integer"},"P1":{"type":"integer"},"P2":{"type":"integer"},"P3":{"type":"integer"}}},"findings":{"type":"array","items":{"type":"object","required":["rule","severity","message"],"properties":{"rule":{"type":"string"},"severity":{"type":"string","enum":["P0","P1","P2","P3"]},"message":{"type":"string"},"file":{"type":["string","null"]},"line":{"type":["integer","null"]},"source":{"type":"string","enum":["server","local"]}}}}}},"path":"\/tools\/check_design_quality","methods":["POST"]},{"name":"get_focal_point_session","description":"Polls a focal-point picker session (from create_focal_point_session). status \u0027pending\u0027 means your user has not confirmed yet \u2014 keep conversing and poll again in a few seconds; \u0027selected\u0027 carries the user-chosen x\/y percentages: RELAY them VERBATIM as focal_x\/focal_y on upload_media\/import_media_from_url or via set_media_focal_point, then tell your user the point was stored. You only pass coordinates through \u2014 never crop, resize or edit the image yourself; the platform regenerates all crops around the stored point automatically. An expired or foreign selection_id is not found \u2014 mint a fresh session.","inputSchema":{"type":"object","required":["sitekey","selection_id"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"selection_id":{"type":"string","pattern":"^[a-f0-9]{32}$","description":"The session id from create_focal_point_session."}}},"outputSchema":{"type":"object","required":["selection_id","status","x","y","image_url"],"properties":{"selection_id":{"type":"string"},"status":{"type":"string","enum":["pending","selected"],"description":"pending: the user has not confirmed yet \u2014 poll again in a few seconds. selected: x\/y carry the user\u0027s choice."},"x":{"type":["integer","null"],"description":"User-chosen horizontal focal percentage (0 left - 100 right); null while pending."},"y":{"type":["integer","null"],"description":"User-chosen vertical focal percentage (0 top - 100 bottom); null while pending."},"image_url":{"type":"string","description":"The image this session is about (echoed)."}},"additionalProperties":false},"path":"\/tools\/get_focal_point_session","methods":["POST"]},{"name":"suggest_alt_text","description":"Proposes alt text for a base64-encoded image (JPEG\/PNG\/GIF\/WebP), in the requested language, using the platform\u0027s configured AI vision provider. Returns available: false with a reason when no provider is configured \u2014 this is a suggestion service, never a substitute for a human checking the description.","inputSchema":{"type":"object","required":["data_base64"],"additionalProperties":false,"properties":{"data_base64":{"type":"string","minLength":1,"description":"The image bytes, base64-encoded. Must sniff as JPEG\/PNG\/GIF\/WebP; max 5 MB decoded."},"langcode":{"type":"string","description":"Language to describe the image in (defaults to en). Ask for each language the subsite publishes in rather than translating one description."},"filename":{"type":"string","description":"Optional original filename; some prompts use it as a hint."},"sitekey":{"type":"string","description":"Optional subsite context. The suggestion itself is not subsite-specific."}}},"outputSchema":{"type":"object","required":["available"],"properties":{"available":{"type":"boolean","description":"False when this platform has no AI vision provider configured; `reason` says which piece is missing."},"alt_text":{"type":["string","null"],"description":"The proposed description. Always show it to a person before storing it \u2014 it is a suggestion, not a verified description."},"langcode":{"type":"string"},"provider":{"type":["string","null"],"description":"The AI provider that produced the suggestion."},"reason":{"type":["string","null"],"description":"Why no suggestion was produced: ai_module_missing, no_provider_configured, invalid_image, or provider_error."}}},"path":"\/tools\/suggest_alt_text","methods":["POST"]},{"name":"get_subsite_inventory","description":"Returns a module-gated census of what already exists on a SiteForge subsite across authoring domains.","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","domains"],"properties":{"sitekey":{"type":"string"},"domains":{"type":"object","description":"Census keyed by authoring domain (pages, libraries, forms, menus, media, data, components, theme).","additionalProperties":{"type":"object","required":["module_installed","count"],"properties":{"module_installed":{"type":"boolean"},"count":{"type":"integer"}},"additionalProperties":true}}},"additionalProperties":false},"path":"\/tools\/get_subsite_inventory","methods":["POST"]},{"name":"create_focal_point_session","description":"Creates a focal-point picker session for YOUR USER and returns a picker_url: an anonymous, login-free page showing the image with a click-to-select crosshair. USE THIS WHEN YOU ARE UNCERTAIN where an image\u0027s focal point belongs \u2014 instead of guessing, send picker_url to your user (\u0027click the most important part of the image and press Confirm\u0027), then poll get_focal_point_session (every few seconds, up to ~1 hour) until it returns the selection, and RELAY the returned x\/y VERBATIM as focal_x\/focal_y on upload_media\/import_media_from_url or via set_media_focal_point. YOU NEVER CROP, RESIZE OR EDIT THE IMAGE YOURSELF and never adjust the user\u0027s coordinates \u2014 storing the point is your entire job; the platform\u0027s image styles regenerate every crop around it automatically. Works BEFORE uploading when you have the image\u0027s URL (your user\u0027s browser loads it directly); for base64 payloads, upload first and run the picker on the stored image\u0027s url.","inputSchema":{"type":"object","required":["sitekey","image_url"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"image_url":{"type":"string","minLength":1,"description":"The image to show on the picker page: an http(s) URL (works BEFORE uploading \u2014 your user\u0027s browser loads it directly) or a site-relative path of already-stored media (e.g. the original url from list_media). javascript:\/data: are refused."}}},"outputSchema":{"type":"object","required":["selection_id","picker_url","expires_in_seconds","instructions"],"properties":{"selection_id":{"type":"string","description":"The session id to poll with get_focal_point_session."},"picker_url":{"type":"string","description":"Send this to YOUR USER. It opens an anonymous, login-free page showing the image; they click the most important part and press Confirm. No platform account is needed."},"expires_in_seconds":{"type":"integer","description":"Session lifetime; after this the selection_id reads as not found \u2014 mint a fresh session."},"instructions":{"type":"string"}},"additionalProperties":false},"path":"\/tools\/create_focal_point_session","methods":["POST"]},{"name":"set_subsite_setting","description":"Sets a single allowlisted third-party setting on a subsite the caller is a member of.","inputSchema":{"type":"object","required":["sitekey","module","key"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"module":{"type":"string","minLength":1,"description":"Provider module of the setting; must be allowlisted with the key."},"key":{"type":"string","minLength":1,"description":"Setting key; the (module, key) pair must be on the shipped allowlist."},"value":{"description":"The value to store for the allowlisted setting. Any JSON type."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/set_subsite_setting","methods":["POST"]},{"name":"set_media_focal_point","description":"Sets the focal point (x\/y as 0-100 percentages from the top-left) of a subsite media image. WHY THIS MATTERS: page images are served through focal-point scale-and-crop styles (styled_urls) \u2014 every placement crops the image to its own aspect ratio AROUND this point, so an unset or wrong focal point cuts faces and subjects out of cards, banners and thumbnails. Set it on every image you place: the subject\u0027s most important detail (a face\u0027s eyes, a product, the text on a sign). IF YOU ARE UNCERTAIN where the focal point should be \u2014 you have not inspected the image, or the subject is ambiguous \u2014 let your user pick it visually: create_focal_point_session gives you a login-free picker page to send them, and get_focal_point_session returns their choice; otherwise state the point you chose so they can correct it. Storing the point is ALL you do \u2014 never crop, resize or edit the image yourself; the platform regenerates every styled derivative around the stored point automatically (re-setting flushes them).","inputSchema":{"type":"object","required":["sitekey","media_id","x","y"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the media must belong to it."},"media_id":{"type":"integer","description":"The media item id (from list_media \/ upload_media)."},"x":{"type":"number","minimum":0,"maximum":100,"description":"Horizontal focal position as a percentage: 0 = left edge, 50 = center, 100 = right edge."},"y":{"type":"number","minimum":0,"maximum":100,"description":"Vertical focal position as a percentage: 0 = top edge, 50 = center, 100 = bottom edge. For portraits aim at the eyes (typically y 25-40), not the geometric center. If you are uncertain where the subject is, ask your user to confirm before setting."},"dry_run":{"type":"boolean","default":false,"description":"Report the current vs proposed focal point without changing anything."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"updated, skipped (focal_point module absent), or dry_run."},"id":{"type":["integer","string","null"],"description":"The media entity id (prefer `id`)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"focal_point":{"type":"object","description":"The stored focal point (relative percentages).","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"media":{"type":"object","description":"The media summary; styled_urls are regenerated around the new focal point \u2014 use those URLs in component image props."},"plan":{"type":"array","description":"On status dry_run: what a real invocation would do.","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}}}},"path":"\/tools\/set_media_focal_point","methods":["POST"]},{"name":"translate_canvas_page","description":"Creates or updates a language translation of a Canvas page. langcode must be one of the subsite\u0027s configured languages. A NEW translation inherits a copy of the source component tree (then translate props via the components argument \u2014 same shape and validation as set_canvas_page_components); title is required when creating. The translation inherits the source page\u0027s published state on create. Builds render the translation at the language-prefixed path once it exists.","inputSchema":{"type":"object","required":["sitekey","id","langcode"],"properties":{"sitekey":{"type":"string","description":"The subsite the page belongs to."},"id":{"type":["integer","string"],"description":"The Canvas page id to translate."},"langcode":{"type":"string","description":"Target language code \u2014 must be one of the subsite\u0027s configured languages (see get_subsite \/ update_subsite_config)."},"title":{"type":"string","description":"Translated page title. REQUIRED when creating a new translation."},"description":{"type":"string","description":"Translated page description."},"components":{"type":"array","description":"The translation\u0027s FULL component tree \u2014 same item shape and validation as set_canvas_page_components ({uuid, component_id, inputs, optional component_version\/parent_uuid\/slot}). Omit on create to inherit a copy of the source tree (then read it back from the response\u0027s top-level components key and rewrite props translated). Omit on update to leave the translation\u0027s tree unchanged.","items":{"type":"object"}},"dry_run":{"type":"boolean","description":"Report the intended change without writing."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"]},"uuid":{"type":["string","null"]},"published":{"type":["boolean","null"],"description":"The TRANSLATION\u0027s published state (inherits the source page\u0027s state on create)."},"warnings":{"type":"array","items":{"type":"string"}},"langcode":{"type":"string"},"is_new_translation":{"type":"boolean"},"components":{"type":"array","description":"The AUTHORITATIVE saved translation tree (top-level key \u2014 not nested) \u2014 rebuild your local copy from this (finding-35 practice).","items":{"type":"object"}}}},"path":"\/tools\/translate_canvas_page","methods":["POST"]},{"name":"list_skills","description":"Lists the authoring skills enabled for a SiteForge subsite (metadata only; use get_skill for the body).","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key whose enabled skills to list."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["skills"],"properties":{"skills":{"type":"array","items":{"type":"object","required":["id","label","description","triggers","provenance","source_license","default_enabled"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"description":{"type":"string"},"triggers":{"type":"array","items":{"type":"string"}},"provenance":{"type":"string"},"source_license":{"type":"string"},"default_enabled":{"type":"boolean"}},"additionalProperties":false}}},"additionalProperties":false},"path":"\/tools\/list_skills","methods":["POST"]},{"name":"get_platform_changelog","description":"Returns what changed on this MCP server since a catalog fingerprint you logged earlier (pass the 12-char hash from tools\/list _meta.siteforge.catalog_fingerprint as since_fingerprint). Entries cover tools added\/removed, description and argument-schema changes, and operator platform notes, newest first. Omit since_fingerprint for the full history.","inputSchema":{"type":"object","properties":{"since_fingerprint":{"type":"string","minLength":8,"description":"The catalog fingerprint you last logged \u2014 the 12-char hash from tools\/list _meta.siteforge.catalog_fingerprint (longer prefixes and the full hash also match). Only entries recorded AFTER that version are returned. Omit for the full history."},"limit":{"type":"integer","minimum":1,"maximum":100,"default":20,"description":"Maximum entries to return (newest first)."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"current_fingerprint":{"type":["string","null"],"description":"The CURRENT 12-char catalog fingerprint \u2014 log this to query incrementally next time."},"up_to_date":{"type":["boolean","null"],"description":"TRUE when since_fingerprint is the current catalog version and nothing changed since; FALSE when changes exist since it. NULL when since_fingerprint was omitted (a full-history query has no reference point)."},"entries":{"type":"array","description":"Changelog entries, newest first.","items":{"type":"object","properties":{"fingerprint":{"type":"string","description":"The 12-char catalog fingerprint this entry produced."},"recorded_at":{"type":["string","null"],"description":"ISO-8601 timestamp the change was recorded."},"kind":{"type":"string","enum":["auto","note"],"description":"auto = diffed from catalog snapshots; note = operator-authored platform note."},"changes":{"type":"array","items":{"type":"string"},"description":"Human-readable change lines."}}}},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/get_platform_changelog","methods":["POST"]},{"name":"list_available_components","description":"Lists the components a SiteForge subsite may place on a Canvas page.","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","module_installed","components"],"properties":{"sitekey":{"type":"string"},"module_installed":{"type":"boolean"},"components":{"type":"array","items":{"type":"object","required":["id","label","category","status"],"properties":{"id":{"type":"string"},"label":{"type":"string"},"category":{"type":"string"},"status":{"type":"string"},"props":{"type":"object","description":"Authorable prop schema for an SDC component (prop name \u2192 {type, title, description, enum, required}). Use these keys inside a component\u0027s `inputs`. Absent for non-SDC components.","additionalProperties":{"type":"object","properties":{"type":{"description":"The prop\u0027s JSON-schema type."},"title":{"type":"string"},"description":{"type":"string"},"enum":{"type":"array","description":"Allowed values, when constrained."},"required":{"type":"boolean"}}}},"slots":{"type":"array","description":"Slot names this component exposes for nested children (used as the `slot` with `parent_uuid`). Absent when it has no slots.","items":{"type":"string"}}}}},"note":{"type":"string"},"catalog":{"type":"array","description":"Catalog components NOT yet cloned into this subsite \u2014 each becomes placeable with one clone_component call ({id, label, category, description}).","items":{"type":"object"}}}},"path":"\/tools\/list_available_components","methods":["POST"]},{"name":"list_subsites","description":"Lists the SiteForge subsites the current user can view.","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","required":["subsites"],"properties":{"subsites":{"type":"array","items":{"type":"object","required":["sitekey","title"],"properties":{"sitekey":{"type":"string"},"title":{"type":"string"}}}}}},"path":"\/tools\/list_subsites","methods":["POST"]},{"name":"set_subsite_homepage","description":"Points a subsite\u0027s homepage at one of its own Canvas pages (or an existing operator node).","inputSchema":{"type":"object","required":["sitekey","type","id"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of, whose homepage is being set."},"type":{"type":"string","enum":["canvas_page","node"],"description":"The homepage target entity type. canvas_page is the supported authoring path; node references an existing operator-provided node only."},"id":{"type":["integer","string"],"description":"The target entity id. A canvas_page id must be a page confined to this subsite; a node id must reference an existing node (never authored by this tool)."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/set_subsite_homepage","methods":["POST"]},{"name":"get_skill","description":"Returns one authoring skill (its raw markdown body included) for a subsite, if that skill is enabled for the subsite.","inputSchema":{"type":"object","required":["sitekey","id"],"properties":{"sitekey":{"type":"string","description":"The subsite key the skill is scoped to."},"id":{"type":"string","description":"The id of the skill to fetch."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["available","id"],"properties":{"available":{"type":"boolean","description":"TRUE when the skill is enabled for the subsite and its body is returned; FALSE for a structured refusal."},"id":{"type":"string"},"label":{"type":"string"},"triggers":{"type":"array","items":{"type":"string"}},"provenance":{"type":"string"},"source_license":{"type":"string"},"body":{"type":"string","description":"The skill\u0027s raw SKILL.md markdown, byte-faithful (free-text path)."},"reason":{"type":"string","description":"Present only on a refusal; the coarse reason the skill is unavailable."}},"additionalProperties":false},"path":"\/tools\/get_skill","methods":["POST"]},{"name":"list_webform_submissions","description":"Reads a subsite webform\u0027s submissions, newest first (capped at 50 per call; paginate with offset). Submissions contain PERSONAL DATA visitors entered \u2014 handle accordingly and only relay to your user what they asked for.","inputSchema":{"type":"object","required":["sitekey","webform"],"properties":{"sitekey":{"type":"string"},"webform":{"type":"string","description":"The webform id \u2014 see list_webforms."},"limit":{"type":"integer","minimum":1,"maximum":50,"default":20},"offset":{"type":"integer","minimum":0,"default":0}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"webform":{"type":"string"},"total":{"type":"integer"},"submissions":{"type":"array","description":"Newest first. Contains PERSONAL DATA visitors entered.","items":{"type":"object","properties":{"id":{"type":"integer"},"created":{"type":"string"},"langcode":{"type":"string"},"data":{"type":"object","description":"Field key =\u003E submitted value."}}}},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/list_webform_submissions","methods":["POST"]},{"name":"list_menus","description":"Lists a subsite\u0027s navigation menus (e.g. main_nav, footer) with their nested link trees ({title, url, weight, enabled, children}). Read this before set_menu_links: that write REPLACES a menu\u0027s whole tree, so start from the current one.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","description":"The subsite key."}}},"outputSchema":{"type":"object","required":["sitekey","menus","warnings"],"properties":{"sitekey":{"type":"string"},"menus":{"type":"array","description":"The subsite\u0027s menus with their current link trees \u2014 the baseline for set_menu_links (which REPLACES a whole tree).","items":{"type":"object","required":["menu","label","links"],"properties":{"menu":{"type":"string","description":"Short menu id to pass to set_menu_links (e.g. main_nav, footer)."},"label":{"type":"string"},"links":{"type":"array","description":"Nested weight-sorted tree: {title, url, weight, enabled, children?}."}}}},"warnings":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"path":"\/tools\/list_menus","methods":["POST"]},{"name":"list_automations","description":"READ-ONLY: lists the subsite\u0027s automations \u2014 trigger, condition tree, action chain, environments, categories, and enabled state \u2014 so you can explain what happens automatically on this site (e.g. on form submissions or publishes). There is NO automation write surface over MCP by design; changes go through the operator UI.","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The member subsite whose automations to list."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"automations":{"type":"array","description":"READ-ONLY. One row per automation bound to the subsite.","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"enabled":{"type":"boolean"},"trigger":{"type":"object","description":"{id, config} \u2014 what starts the automation."},"condition_tree":{"type":"object","description":"The gating conditions."},"action_chain":{"type":"array","description":"The actions run in order (credential-shaped keys redacted)."},"environments":{"type":"array","items":{"type":"string"}},"categories":{"type":"array","items":{"type":"string"}}}}},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/list_automations","methods":["POST"]},{"name":"list_redirects","description":"Lists the URL redirects owned by a subsite the caller is a member of.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; only its redirects are returned."}}},"outputSchema":{"type":"object","required":["sitekey","module_installed","redirects"],"properties":{"sitekey":{"type":"string"},"module_installed":{"type":"boolean"},"redirects":{"type":"array","items":{"type":"object","required":["id","source","destination","status_code"],"properties":{"id":{"type":"integer"},"source":{"type":"string"},"destination":{"type":"string"},"status_code":{"type":"integer"}}}}}},"path":"\/tools\/list_redirects","methods":["POST"]},{"name":"list_canvas_pages","description":"Lists the Drupal Canvas pages attached to a SiteForge subsite (deprecated; use list_subsite_pages).","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","pages"],"properties":{"sitekey":{"type":"string"},"pages":{"type":"array","items":{"type":"object","required":["id","title"],"properties":{"id":{"type":"integer"},"title":{"type":"string"}}}}}},"path":"\/tools\/list_canvas_pages","methods":["POST"]},{"name":"create_redirect","description":"Creates a URL redirect confined to a subsite the caller is a member of (source path, destination, HTTP status code).","inputSchema":{"type":"object","required":["sitekey","source","destination"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the redirect is confined to it."},"source":{"type":"string","minLength":1,"description":"The source path to redirect from (e.g. old\/path). A leading slash is optional."},"destination":{"type":"string","minLength":1,"description":"The redirect destination: an internal Drupal path (e.g. node\/1) or an absolute URL."},"status_code":{"type":"integer","enum":[301,302,303,307,308],"default":301,"description":"The HTTP redirect status code. Defaults to 301 (permanent)."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/create_redirect","methods":["POST"]},{"name":"clone_component","description":"Clones a catalog component into a member subsite so it becomes placeable there: pass the catalog id from list_available_components\u0027 catalog section (the not-yet-cloned set). The clone ships in the site\u0027s active framework variant and appears under its sdc.\u003Ctheme\u003E.\u003Cclone\u003E id on the next list. Re-cloning with force: true overwrites a customized clone \u2014 use only deliberately.","inputSchema":{"type":"object","required":["sitekey","component"],"properties":{"sitekey":{"type":"string","description":"The member subsite to clone into."},"component":{"type":"string","description":"The catalog id \u2014 see the catalog section of list_available_components."},"force":{"type":"boolean","description":"Overwrite an existing clone (loses local customizations) \u2014 use only deliberately."},"dry_run":{"type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string"},"catalog_id":{"type":"string"},"component_id":{"type":"string","description":"The placeable sdc.\u003Ctheme\u003E.\u003Cclone\u003E id for set_canvas_page_components."},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/clone_component","methods":["POST"]},{"name":"read_canvas_page","description":"Reads a single Drupal Canvas page belonging to a SiteForge subsite, including its component tree (mirroring the shape set_canvas_page_components accepts).","inputSchema":{"type":"object","required":["sitekey","page_id"],"properties":{"sitekey":{"type":"string","description":"The subsite key."},"page_id":{"type":"integer","description":"The Canvas page entity id."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","id","title","components"],"properties":{"sitekey":{"type":"string"},"id":{"type":"integer"},"title":{"type":"string"},"description":{"type":"string"},"path":{"type":"string"},"components":{"type":"array","description":"The page\u0027s component tree, projected in the same shape set_canvas_page_components accepts, so a written tree can be read back and verified.","items":{"type":"object","required":["uuid","component_id","component_version","inputs"],"properties":{"uuid":{"type":"string","description":"The component instance\u0027s unique id in the tree."},"component_id":{"type":"string","description":"The Canvas component config entity id, e.g. sdc.\u003Cprovider\u003E.\u003Cname\u003E."},"component_version":{"type":"string","description":"The component version the instance is pinned to."},"inputs":{"type":"object","description":"The component\u0027s prop inputs, keyed by prop name.","additionalProperties":true},"parent_uuid":{"type":"string","description":"The uuid of the parent component instance; absent for a root component."},"slot":{"type":"string","description":"The parent component\u0027s slot this instance is placed in; absent for a root component."},"label":{"type":"string","description":"The author-facing label for this component instance, when set."}}}}}},"path":"\/tools\/read_canvas_page","methods":["POST"]},{"name":"list_library_proposals","description":"Lists agent-submitted library proposals with their FSM status \u2014 the poll surface for \u0022was my proposal approved?\u0022. mine:true returns only the calling token\u0027s proposals.","inputSchema":{"type":"object","additionalProperties":false,"properties":{"status":{"type":"array","items":{"type":"string","enum":["proposed","approved","rejected","blocked","forging","offered","failed"]},"description":"Optional filter: return only proposals in one of these FSM states."},"npm_package":{"type":"string","description":"Optional filter: return only proposals for this npm package."},"mine":{"type":"boolean","description":"When true, return ONLY proposals submitted by the calling token."}}},"outputSchema":{"type":"object","required":["proposals"],"properties":{"proposals":{"type":"array","items":{"type":"object","required":["uuid","npm_package","resolved_version","status","request_count"],"properties":{"uuid":{"type":"string"},"npm_package":{"type":"string"},"resolved_version":{"type":"string"},"status":{"type":"string"},"request_count":{"type":"integer"},"decided_at":{"type":["integer","null"]},"descriptor_id":{"type":["string","null"]}}}}}},"path":"\/tools\/list_library_proposals","methods":["POST"]},{"name":"get_subsite_urls","description":"Returns the browser URLs where a subsite can be viewed: the published site (primary domain or local host), per-language variants, and the operator dashboard.","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","lifecycle_state","published_url","published_url_source","published_urls","local_site_url","is_live","latest_build","dashboard_url","editing_host_url"],"properties":{"sitekey":{"type":"string"},"lifecycle_state":{"type":"string","description":"The subsite lifecycle state. A published_url may 404 until the site has been built and published."},"published_url":{"type":["string","null"],"description":"Open this in a browser to see the site as visitors do: the registered primary domain when one exists, otherwise the local host serving the published static build. Null when neither resolves. Check is_live: while false, published URLs 404 \u2014 use build_site to get a browsable preview, or build_site with publish: true to go live."},"published_url_source":{"type":["string","null"],"enum":["primary_domain","local_ddev_host",null],"description":"Where published_url came from: a registered primary domain, or the local development host."},"published_urls":{"type":"array","description":"Per-language published URLs. A language bound to its own primary domain gets that domain; other languages share the language-agnostic URL. Empty when the subsite declares no languages or no URL resolves.","items":{"type":"object","required":["language","url"],"properties":{"language":{"type":"string","description":"The langcode (e.g. nl, fr)."},"url":{"type":"string"}},"additionalProperties":false}},"local_site_url":{"type":["string","null"],"description":"The local-development host that always serves the published static build, even when a registered primary domain (which may not resolve locally) is the canonical published_url. Null outside local development."},"is_live":{"type":["boolean","null"],"description":"Whether a published build currently serves the published URLs. False means those URLs 404 until a build is published (see build_site). Null when the builder module is absent and liveness is unknown."},"latest_build":{"type":["object","null"],"description":"The subsite\u0027s most recent build, or null when none exists. preview_url is set only for a succeeded build; run build_site to (re)build and expose a fresh preview.","properties":{"build_id":{"type":"string","description":"The build UUID."},"status":{"type":"string","description":"running, succeeded, or failed."},"preview_url":{"type":["string","null"]},"memo":{"type":["string","null"],"description":"The build memo (why the build was made); null for dashboard\/drush builds."}}},"dashboard_url":{"type":["string","null"],"description":"The subsite\u0027s operator dashboard on the platform host. Requires an operator browser login; not reachable with an MCP bearer token."},"editing_host_url":{"type":["string","null"],"description":"Local-development convenience host that lands an operator on the subsite console and serves the live (draft) Drupal render of the subsite. Null outside local development."}},"additionalProperties":false},"path":"\/tools\/get_subsite_urls","methods":["POST"]},{"name":"list_subsite_pages","description":"Lists a SiteForge subsite\u0027s Canvas pages with their draft\/published state and path.","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","pages"],"properties":{"sitekey":{"type":"string"},"pages":{"type":"array","items":{"type":"object","required":["id","uuid","title","changed","published","status","path"],"properties":{"id":{"type":"integer"},"uuid":{"type":"string"},"title":{"type":"string"},"changed":{"type":"integer"},"published":{"type":"boolean"},"status":{"type":"string","enum":["published","draft"]},"path":{"type":"string"}}}}}},"path":"\/tools\/list_subsite_pages","methods":["POST"]},{"name":"delete_canvas_page","description":"Deletes a Canvas page in a subsite the caller is a member of.","inputSchema":{"type":"object","required":["sitekey","id"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of and the page belongs to."},"id":{"type":"integer","description":"The Canvas page entity id to delete."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/delete_canvas_page","methods":["POST"]},{"name":"create_canvas_page","description":"Creates a draft Canvas page in a subsite the caller is a member of. DESIGN QUALITY: call design_guidance BEFORE laying out the page \u2014 it returns the site\u0027s on-brand rules (Impeccable provider); check_design_quality validates the finished page.","inputSchema":{"type":"object","required":["sitekey","title"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the new page is scoped to it."},"title":{"type":"string","minLength":1,"description":"The Canvas page title."},"uuid":{"type":"string","description":"Optional caller-supplied idempotency key; a repeated create with the same uuid returns the existing page instead of a duplicate."},"components":{"type":"array","description":"Optional (typically blank) component tree. Non-trivial component authoring is handled by set_canvas_page_components."},"publish":{"type":"boolean","description":"Request that the page be published. Honored live only with publish authority (may_publish); otherwise the page is saved as a draft."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/create_canvas_page","methods":["POST"]},{"name":"build_site","description":"Runs a static build of the subsite and returns a browsable preview URL; publish: true additionally promotes the build to the live published site. Requires a one-line memo (why this build is being made) \u2014 it is stored on the build record and shown to operators in the build history.","inputSchema":{"type":"object","required":["sitekey","memo"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the build is confined to it."},"memo":{"type":"string","minLength":3,"maxLength":255,"description":"REQUIRED build memo: one line on why this build is being made and what changed (e.g. \u0027Homepage card image swapped to focal crop\u0027). Stored on the build record and shown to operators in the build history \u2014 write it for a human reading the ledger later."},"publish":{"type":"boolean","default":false,"description":"Also promote the successful build to the live published site. Requires publish authority; without it the build degrades to preview-only with a warning."},"dry_run":{"type":"boolean","default":false,"description":"Report what a real invocation would do without building anything."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"created (build succeeded), skipped (builder module absent), failed (build_already_running \/ renderer_unavailable \/ build_failed), or dry_run."},"id":{"type":["integer","string","null"],"description":"The build record id (prefer `id`)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"],"description":"The build UUID."},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean","description":"Whether this build is now the live published site (via publish: true or the platform\u0027s auto-publish setting). When false the build is reachable only at preview_url."},"sitekey":{"type":"string"},"preview_url":{"type":["string","null"],"description":"Open this in a browser to see the freshly built site immediately \u2014 served regardless of whether the site is published."},"page_count":{"type":"integer","description":"Number of pages the renderer exported."},"output_size_bytes":{"type":"integer"},"lifecycle_state":{"type":"string","description":"The subsite lifecycle state after the build."},"error":{"type":"string","description":"On status failed: a stable machine-readable token (build_already_running, renderer_unavailable, build_failed)."},"detail":{"type":"string","description":"On status failed: the human-readable failure detail."},"plan":{"type":"array","description":"On status dry_run: the ops a real invocation would perform.","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}},"memo":{"type":"string","description":"The memo stamped on this build (echoed)."}}},"path":"\/tools\/build_site","methods":["POST"]},{"name":"update_canvas_page","description":"Updates the metadata of a Canvas page in a subsite the caller is a member of.","inputSchema":{"type":"object","required":["sitekey","id"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of and the page belongs to."},"id":{"type":"integer","description":"The Canvas page entity id to update."},"title":{"type":"string","minLength":1,"description":"New page title (only changed when supplied)."},"description":{"type":"string","description":"New meta description (only changed when supplied)."},"path":{"type":"string","description":"New URL alias, e.g. \/about (only changed when supplied; empty clears it)."},"publish":{"type":"boolean","description":"Request that the page be published. Honored live only with publish authority (may_publish); otherwise the page stays a draft."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/update_canvas_page","methods":["POST"]},{"name":"list_media","description":"Lists a subsite\u0027s image media with browser-reachable URLs, alt text, focal point and focal-crop styled_urls. Use styled_urls.card\/wide in component image_url props (they crop around the focal point so the subject stays visible); when focal_point is null, set one with set_media_focal_point \u2014 ask your user if uncertain.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","description":"The subsite key."}}},"outputSchema":{"type":"object","required":["sitekey","media","warnings"],"properties":{"sitekey":{"type":"string"},"media":{"type":"array","description":"The subsite\u0027s image media. Prefer styled_urls.* for component image props (focal-point aware crops); url is the uncropped original.","items":{"type":"object","required":["id","label","url","alt","mime","size_bytes"],"properties":{"id":{"type":"integer"},"label":{"type":"string"},"url":{"type":"string","description":"Browser-reachable absolute URL \u2014 paste into image_url."},"alt":{"type":"string","description":"Alt text \u2014 paste into image_alt."},"mime":{"type":"string"},"size_bytes":{"type":"integer"},"width":{"type":"integer"},"height":{"type":"integer"},"focal_point":{"type":["object","null"],"description":"The image\u0027s focal point as 0-100 percentages from the top-left, or null when unset (styled renditions center-crop; set one with set_media_focal_point).","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"styled_urls":{"type":["object","null"],"description":"Focal-point scale-and-crop derivative paths \u2014 USE THESE in component image props instead of the original url: card (640x360) for cards\/tiles, wide (1600x900) for heroes\/banners. Root-RELATIVE by design so static builds export the image instead of hotlinking the platform host; prepend the platform origin only if you need to view one in a browser.","properties":{"card":{"type":"string"},"wide":{"type":"string"}}},"css_object_position":{"type":["string","null"],"description":"The focal point as a ready CSS position value (e.g. \u002757% 28%\u0027), null when unset. WHENEVER YOU WRITE CSS\/SCSS for this image with object-fit: cover or background-size: cover, use it as object-position \/ background-position \u2014 browser-side crops move with the viewport, and on mobile breakpoints a centered default pushes off-center subjects out of frame; mirroring the focal point keeps every breakpoint\u0027s crop on the subject, consistent with the server-side styled_urls."},"dimensions_assessment":{"type":"object","description":"Per rendition style (card\/wide): {target: \u0027WxH\u0027, sufficient: bool (source fills the target without upscaling), retina: bool (\u003E= 2x target, crisp on high-DPI), upscale_factor?: number (present when insufficient \u2014 how much the derivative stretches)}. If a style you intend to use is insufficient, source a larger image BEFORE placing it."}}}},"warnings":{"type":"array","items":{"type":"string"}}},"additionalProperties":false},"path":"\/tools\/list_media","methods":["POST"]},{"name":"list_site_templates","description":"Lists the published site templates \u2014 versioned, captured site slices (pages, menus, components, theme) that apply_site_template can instantiate into a subsite. Richer than create_site\u0027s blank\/starter seeds: a template ships a complete starter site. Each row lists the parts its current version captured.","inputSchema":{"type":"object","properties":{},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"templates":{"type":"array","items":{"type":"object","properties":{"id":{"type":"integer","description":"Template id \u2014 pass to apply_site_template."},"label":{"type":"string"},"visibility":{"type":"string"},"published_version":{"type":["string","null"],"description":"Current published semver; apply always uses this version."},"tags":{"type":"array","items":{"type":"string"}},"source_sitekey":{"type":["string","null"],"description":"The subsite the template was captured from."},"parts":{"type":"array","items":{"type":"string"},"description":"Part ids the current version captured (e.g. content, menus, components, theme) \u2014 the selectable units for apply_site_template."}}}},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/list_site_templates","methods":["POST"]},{"name":"import_media_from_url","description":"Server-fetches a public, direct image URL (JPEG\/PNG\/GIF\/WebP, max 5 MB, no redirects) and stores it as subsite media, returning its browser URL for component image_url props.","inputSchema":{"type":"object","required":["sitekey","url","alt"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the media is bound to it."},"url":{"type":"string","minLength":1,"description":"Direct public http(s) image URL. Redirects are not followed; hosts resolving to private\/internal addresses are refused; max 5 MB; must sniff as JPEG\/PNG\/GIF\/WebP."},"alt":{"type":"string","minLength":1,"description":"Alt text for the image (required \u2014 accessibility is not optional)."},"filename":{"type":"string","description":"Suggested filename; defaults to the URL\u0027s basename. Sanitized, extension replaced by the sniffed type."},"label":{"type":"string","description":"Media label shown to operators; defaults to the sanitized filename."},"dry_run":{"type":"boolean","default":false,"description":"Validate the URL\u0027s shape and public destination WITHOUT fetching or storing anything."},"focal_x":{"type":"number","minimum":0,"maximum":100,"description":"Optional focal point x (0 left - 100 right, percentage). Set it whenever you know where the image\u0027s subject is \u2014 styled renditions crop around it. Supply both focal_x and focal_y or neither."},"focal_y":{"type":"number","minimum":0,"maximum":100,"description":"Optional focal point y (0 top - 100 bottom, percentage). If you are uncertain where the focal point of this new image belongs, ask your user to confirm instead of guessing; without one, renditions center-crop."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"created, skipped (identical bytes already stored, transfer failure, or media surface unavailable), or dry_run."},"id":{"type":["integer","string","null"],"description":"The media entity id (prefer `id`)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings \u2014 including dimension-fitness warnings when the source is smaller than a rendition target (act on those NOW: re-source a larger image while you still have the original at hand, instead of discovering stretched output at build time)."},"media":{"type":"object","description":"The stored (or matched) media summary. Use styled_urls.card \/ styled_urls.wide (root-RELATIVE paths, so static builds export the image) in component image props; focal_point is null until one is set (renditions then center-crop).","properties":{"id":{"type":"integer"},"label":{"type":"string"},"url":{"type":["string","null"]},"alt":{"type":"string"},"mime":{"type":["string","null"]},"size_bytes":{"type":["integer","null"]},"width":{"type":"integer"},"height":{"type":"integer"},"dimensions_assessment":{"type":"object","description":"Per rendition style (card\/wide): {target: \u0027WxH\u0027, sufficient: bool (source fills the target without upscaling), retina: bool (\u003E= 2x target, crisp on high-DPI), upscale_factor?: number (present when insufficient \u2014 how much the derivative stretches)}. If a style you intend to use is insufficient, source a larger image BEFORE placing it."}}},"source_url":{"type":"string","description":"The URL that was imported (echoed on create)."},"plan":{"type":"array","description":"On status dry_run: what a real invocation would do.","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}},"focal_point":{"type":"object","description":"The stored focal point (relative percentages), present when focal_x\/focal_y were supplied.","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}}}},"path":"\/tools\/import_media_from_url","methods":["POST"]},{"name":"get_subsite","description":"Returns a SiteForge subsite\u0027s configuration snapshot (label, theme, languages, build paths, status).","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The subsite key."}},"additionalProperties":false},"outputSchema":{"type":"object","required":["sitekey","label","theme","languages","output_subdir","include_paths","exclude_paths","status"],"properties":{"sitekey":{"type":"string"},"label":{"type":"string"},"theme":{"type":"string"},"languages":{"type":"array","items":{"type":"string"}},"output_subdir":{"type":"string"},"include_paths":{"type":"array","items":{"type":"string"}},"exclude_paths":{"type":"array","items":{"type":"string"}},"status":{"type":"string"},"homepage":{"type":["object","null"],"description":"The subsite\u0027s homepage target (the read side of set_subsite_homepage), or null when none is set.","properties":{"type":{"type":"string","description":"The homepage entity type: \u0027canvas_page\u0027 or \u0027node\u0027."},"id":{"type":["integer","string","null"]},"title":{"type":"string"}}}},"additionalProperties":false},"path":"\/tools\/get_subsite","methods":["POST"]},{"name":"set_canvas_page_components","description":"Sets a Canvas page\u0027s component tree, validating each component\u0027s prop inputs before save; an invalid tree is rejected with structured violations and no write. DESIGN QUALITY: before composing or restyling a page, call design_guidance for the site\u0027s on-brand rules (spacing, hierarchy, color usage), and run check_design_quality on the result \u2014 plain default-styled stacks read as unfinished.","inputSchema":{"type":"object","required":["sitekey","id","components"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of and the page belongs to."},"id":{"type":"integer","description":"The Canvas page entity id whose component tree to set."},"components":{"type":"array","description":"The ordered, nested component tree. Each item places one component and carries its prop inputs. Component ids come from list_available_components; invalid prop inputs are rejected with structured violations and no write.","items":{"type":"object","required":["uuid","component_id","inputs"],"additionalProperties":false,"properties":{"uuid":{"type":"string","minLength":1,"description":"A unique id for this component instance in the tree (a UUID)."},"component_id":{"type":"string","minLength":1,"description":"The Canvas component config entity id, e.g. sdc.\u003Cprovider\u003E.\u003Cname\u003E."},"component_version":{"type":"string","description":"Optional component version identifier; the component\u0027s active version is used when omitted or left blank."},"parent_uuid":{"type":["string","null"],"description":"The uuid of the parent component instance, or null\/absent for a root component."},"slot":{"type":["string","null"],"description":"The parent component\u0027s slot machine name this instance is placed in; required when parent_uuid is set."},"inputs":{"type":"object","description":"The component\u0027s prop inputs, keyed by prop name. Validated against the component\u0027s prop schema before save.","additionalProperties":true},"label":{"type":["string","null"],"description":"Optional author-facing label for this component instance."}}}}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"},"description":"Write-mechanics warnings PLUS automatic design review: lines prefixed \u0027Design [severity]:\u0027 are Impeccable findings from scanning the saved page\u0027s render. They are advisory (the write succeeded) \u2014 resolve them with design_guidance, or run check_design_quality for the full verdict."},"published":{"type":"boolean"},"components":{"type":"array","description":"The AUTHORITATIVE saved tree after this write (top-level key) \u2014 items as {uuid, component_id, component_version, parent_uuid, slot, inputs}. Always rebuild your local copy from THIS, never from what you sent.","items":{"type":"object"}}}},"path":"\/tools\/set_canvas_page_components","methods":["POST"]},{"name":"create_webform","description":"Creates a webform on a member subsite: title + fields[] ({key, type: text|email|textarea|tel|number|date|select|checkbox, label, required?, options? for select}) with an optional confirmation_message and notification_email. The static-embed snippet is enabled automatically; place the returned embed_markup on a page (HTML-capable prop) and builds bake it into a working form. Submissions are read via list_webform_submissions.","inputSchema":{"type":"object","required":["sitekey","title","fields"],"properties":{"sitekey":{"type":"string","description":"The member subsite the webform belongs to."},"title":{"type":"string","description":"Human form title (the machine id derives from it)."},"fields":{"type":"array","minItems":1,"description":"The form\u0027s fields, in order.","items":{"type":"object","required":["key","label"],"properties":{"key":{"type":"string","description":"Machine name: lowercase a-z, 0-9, underscores."},"type":{"type":"string","enum":["text","email","textarea","tel","number","date","select","checkbox"],"default":"text"},"label":{"type":"string"},"required":{"type":"boolean","default":false},"options":{"type":"array","items":{"type":"string"},"description":"Choices \u2014 required for type select."}}}},"confirmation_message":{"type":"string","description":"Shown after a successful submit."},"notification_email":{"type":"string","description":"E-mail address notified on each submission."},"dry_run":{"type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string"},"webform_id":{"type":"string"},"embed_markup":{"type":"string","description":"Place this on a page (HTML-capable prop); build_site bakes it into a working form."},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/create_webform","methods":["POST"]},{"name":"delete_redirect","description":"Deletes a URL redirect owned by a subsite the caller is a member of. Refuses redirects owned by any other subsite.","inputSchema":{"type":"object","required":["sitekey","id"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; only redirects owned by it may be deleted."},"id":{"type":"integer","minimum":1,"description":"The id (rid) of the redirect to delete. Must belong to the given subsite."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/delete_redirect","methods":["POST"]},{"name":"update_subsite_config","description":"Updates a member subsite configuration (label, theme, languages, output\/include\/exclude paths). Partial update: only supplied fields change.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","description":"Machine-name of the subsite to update. Used only to resolve the subsite; the sitekey is immutable and any attempt to change it is rejected."},"label":{"type":"string","description":"Human-readable subsite label."},"theme":{"type":"string","description":"Machine name of an installed theme."},"languages":{"type":"array","items":{"type":"string"},"description":"Enabled language codes for the subsite."},"output_subdir":{"type":"string","description":"Relative output subdirectory under the SiteForge html root."},"include_paths":{"type":"array","items":{"type":"string"},"description":"URL path patterns to include in the export."},"exclude_paths":{"type":"array","items":{"type":"string"},"description":"URL path patterns to exclude from the export."},"dry_run":{"type":"boolean","description":"When true, validate and preview without writing."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"}}},"path":"\/tools\/update_subsite_config","methods":["POST"]},{"name":"upload_file","description":"Uploads a base64-encoded NON-IMAGE file (PDF, Word, Excel, PowerPoint, OpenDocument, plain text, CSV; max 5 MB decoded) as subsite media and returns its browser URL. A title is REQUIRED and is never derived from the filename \u2014 ask your user for one rather than inventing it. Use upload_media for images (JPEG\/PNG\/GIF\/WebP); this tool refuses them. The file type is decided by sniffing the bytes, so a mislabelled extension changes nothing. Re-uploading identical bytes returns the existing media instead of storing a duplicate.","inputSchema":{"type":"object","required":["sitekey","data_base64","title"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the file is bound to it."},"data_base64":{"type":"string","minLength":1,"description":"The file bytes, base64-encoded. Must sniff as PDF, Word (doc\/docx), Excel (xls\/xlsx), PowerPoint (ppt\/pptx), OpenDocument (odt\/ods\/odp), plain text or CSV; max 5 MB decoded. Images are refused here \u2014 use upload_media. The type is read from the bytes, so a wrong extension neither helps nor hurts."},"title":{"type":"string","minLength":1,"description":"The document\u0027s title \u2014 REQUIRED, and never derived from the filename. This is the text a visitor reads and clicks on the page, so it must be something a person chose: ask your user rather than inventing one."},"filename":{"type":"string","description":"Suggested filename; sanitized, and its extension is replaced by the sniffed file type. It names the file on disk only \u2014 it is not the title and never decides what is accepted."},"dry_run":{"type":"boolean","default":false,"description":"Validate the payload (decode, title, sniff, size, duplicate check) without storing anything."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"created, skipped (identical bytes already stored, or the media surface\/bundle is unavailable), or dry_run."},"id":{"type":["integer","string","null"],"description":"The media entity id (prefer `id`)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id`."},"uuid":{"type":["string","null"]},"duplicate":{"type":"boolean","description":"TRUE when these exact bytes were already stored in this subsite and the existing media was returned instead. Its stored title is left untouched \u2014 a re-upload does not rename it."},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings."},"media":{"type":"object","description":"The stored (or matched) file summary. `url` is the browser-reachable address to link to from a page.","properties":{"id":{"type":"integer"},"bundle":{"type":"string","description":"The media bundle chosen from the sniffed file type."},"title":{"type":"string"},"url":{"type":["string","null"]},"mime":{"type":["string","null"]},"size_bytes":{"type":["integer","null"]}}},"plan":{"type":"array","description":"On status dry_run: what a real invocation would do.","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}}}},"path":"\/tools\/upload_file","methods":["POST"]},{"name":"list_available_libraries","description":"Lists the offered frontend libraries an agent may attach to a subsite via create_site (dynamic \u2014 the offered set changes as operators approve proposals).","inputSchema":{"type":"object","additionalProperties":false,"properties":{"compile_mode":{"type":"string","enum":["precompiled","per_subsite"],"description":"Optional filter: return only libraries of this compile tier."},"include_unofferable":{"type":"boolean","description":"When true, also include artifact-backed descriptors whose offerable flag is off (default false: only the live offered set)."}}},"outputSchema":{"type":"object","required":["pack_generation","libraries"],"properties":{"pack_generation":{"type":"string","description":"The verified pack\u0027s deterministic generation id, or \u0027\u0027 when no verified pack is installed."},"libraries":{"type":"array","items":{"type":"object","required":["id","npm_package","version","kind","license","compile_mode","source","bytes"],"properties":{"id":{"type":"string"},"npm_package":{"type":["string","null"]},"version":{"type":["string","null"]},"kind":{"type":"string"},"license":{"type":["string","null"]},"compile_mode":{"type":"string"},"source":{"type":"string","description":"\u0027plugin\u0027 (code-defined #[Library]) or \u0027descriptor\u0027 (data-defined config entity)."},"bytes":{"type":"integer"},"offerable":{"type":"boolean"}}}}}},"path":"\/tools\/list_available_libraries","methods":["POST"]},{"name":"list_subsite_setting_keys","description":"Lists the (module, key) third-party setting pairs the set_subsite_setting write tool is allowed to write.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of. Used only to confine the call; the allowlist itself is platform-wide."}}},"outputSchema":{"type":"object","required":["sitekey","pairs"],"properties":{"sitekey":{"type":"string"},"pairs":{"type":"array","description":"The (module, key) third-party setting pairs set_subsite_setting is allowed to write.","items":{"type":"object","required":["module","key"],"properties":{"module":{"type":"string","description":"The provider module of the writable setting."},"key":{"type":"string","description":"The writable setting key."}}}}}},"path":"\/tools\/list_subsite_setting_keys","methods":["POST"]},{"name":"capture_site_template","description":"Captures a member subsite AS a reusable site template: the selected parts (default: everything capturable) snapshot into version 1.0.0. The template stays DRAFT awaiting operator review \u2014 it appears in list_site_templates only after the operator publishes it; tell your user to expect that review step. Optional parts[] narrows the capture; changelog describes what this template is.","inputSchema":{"type":"object","required":["sitekey","label"],"properties":{"sitekey":{"type":"string","description":"The member subsite to capture."},"label":{"type":"string","description":"Human name of the template (e.g. \u0022Festival starter\u0022)."},"parts":{"type":"array","items":{"type":"string"},"description":"Part ids to capture (components, theme, content, settings, menus, media, recipes). Omit to capture everything capturable."},"changelog":{"type":"string","description":"What this template is \/ why it was captured."},"dry_run":{"type":"boolean"}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string"},"template_id":{"type":"integer"},"version":{"type":"string"},"captured_parts":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"},"description":"Always includes the DRAFT-awaiting-operator-review notice \u2014 relay it to your user."}}},"path":"\/tools\/capture_site_template","methods":["POST"]},{"name":"apply_site_template","description":"Instantiates a published site template (see list_site_templates) into a member subsite: pages, menus, components, theme and settings from the template\u0027s current version land in the subsite, and an instantiation link is recorded for future template updates. Optional parts[] selects which captured parts apply (default: all). Best applied to a FRESH subsite right after create_site \u2014 parts overwrite same-named resources. Missing\/unavailable parts are skipped with warnings, never fatal. Changes appear on the next build_site.","inputSchema":{"type":"object","required":["sitekey","template_id"],"properties":{"sitekey":{"type":"string","description":"The member subsite to instantiate the template into. Best a FRESH subsite (right after create_site) \u2014 parts overwrite same-named resources."},"template_id":{"type":["integer","string"],"description":"The site template id \u2014 see list_site_templates."},"parts":{"type":"array","items":{"type":"string"},"description":"Part ids to apply (subset of the template\u0027s captured parts, e.g. [\u0022content\u0022, \u0022menus\u0022, \u0022theme\u0022]). Omit to apply everything the version captured."},"dry_run":{"type":"boolean","description":"Report what would be applied without writing."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The recorded template_instantiation id (the living link future template updates flow through)."},"uuid":{"type":["string","null"]},"template_id":{"type":"integer"},"template_label":{"type":"string"},"version":{"type":"string","description":"The semver that was applied."},"applied_parts":{"type":"array","items":{"type":"string"}},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/apply_site_template","methods":["POST"]},{"name":"set_theme_library","description":"Sets a subsite\u0027s active theme library (the CSS framework builds render with \u2014 see get_theme_library for the available set) and\/or its per-library options (e.g. {colors: {primary, accent}}). Supply library, options, or both; options are validated against the library\u0027s schema and an invalid payload changes nothing. Switching the library also re-ships cloned component variants to match the new framework. Changes appear on the next build_site. COLORS: never hand-pick or keep framework defaults \u2014 FIRST call design_guidance command \u0022palette\u0022 (derives a brand-true, WCAG-checked palette from the site\u0027s stored brief), store its result here, and re-run check_design_quality after restyling. Typography: design_guidance command \u0022typeset\u0022.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"library":{"type":"string","description":"Library id to activate (see get_theme_library.available). Omit to keep the current library and only update options."},"options":{"type":"object","description":"Per-library options payload (e.g. {colors: {primary: \u0027#2dd4bf\u0027, accent: \u0027#facc15\u0027}}). Validated against the library\u0027s schema \u2014 an invalid payload changes NOTHING."},"dry_run":{"type":"boolean","default":false,"description":"Report the intended change without writing."},"allow_default_palette":{"type":"boolean","description":"Pass true ONLY when the USER explicitly asked for framework default colors. Without it, a palette consisting entirely of verbatim Tailwind stock hexes is rejected \u2014 derive brand colors via design_guidance command \u0027palette\u0027 instead."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"updated, skipped (module absent), rolled_back, or dry_run."},"id":{"type":["integer","string","null"]},"created_id":{"type":["integer","string","null"]},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"active_library":{"type":["string","null"],"description":"The active library after the write."},"options":{"type":["object","null"],"description":"The options payload that was applied (echoed), null when only the library changed."},"plan":{"type":"array","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}}}},"path":"\/tools\/set_theme_library","methods":["POST"]},{"name":"get_theme_library","description":"Returns the subsite\u0027s active theme library (the CSS framework its builds render with \u2014 e.g. tailwind, bootstrap), its per-library options (colors, typography), and the available libraries. A null active_library means builds render frameworkless \u2014 set one with set_theme_library.","inputSchema":{"type":"object","required":["sitekey"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","description":"The subsite key."}}},"outputSchema":{"type":"object","required":["sitekey","active_library","options","available","warnings"],"properties":{"sitekey":{"type":"string"},"active_library":{"type":["string","null"],"description":"The CSS framework builds render with (e.g. tailwind, bootstrap). Null = frameworkless builds \u2014 set one with set_theme_library."},"options":{"type":["object","null"],"description":"The active library\u0027s operator options (colors, typography), null when none are set."},"available":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"label":{"type":"string"},"options_schema":{"type":"object","description":"The library\u0027s options schema (types\/properties\/enums) \u2014 what set_theme_library\u0027s options payload is validated against. Read this before composing options."}}}},"warnings":{"type":"array","items":{"type":"string"}},"design_guidance_hint":{"type":"string","description":"Present when the design surface is installed: how to choose colors\/typography properly (design_guidance command \u0027palette\u0027 \/ \u0027typeset\u0027) instead of hand-picking or keeping framework defaults."}},"additionalProperties":false},"path":"\/tools\/get_theme_library","methods":["POST"]},{"name":"propose_library","description":"Proposes an npm package NOT currently offered for operator review. The proposal is screened and queued; it is never auto-forged (an operator must approve).","inputSchema":{"type":"object","additionalProperties":false,"required":["npm_package"],"properties":{"npm_package":{"type":"string","maxLength":214,"description":"The npm package name to propose for operator review."},"requested_range":{"type":"string","description":"The requested semver range (\u0027\u0027 \/ \u0027*\u0027 resolve to latest)."},"rationale":{"type":"string","maxLength":2000,"description":"Agent-supplied justification for the proposal."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","enum":["created","deduplicated","already_offered","blocked"],"description":"created: a fresh proposal was registered; deduplicated: an existing non-terminal proposal was matched (request_count bumped); already_offered: the package is already an offered library (id points at the descriptor); blocked: a fresh proposal was screened and blocked."},"id":{"type":["string","integer","null"],"description":"The proposal uuid (created\/deduplicated\/blocked) or the offered descriptor id (already_offered)."},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/propose_library","methods":["POST"]},{"name":"create_site","description":"Creates a new DRAFT subsite from a caller-supplied DNS-label sitekey and human label, and makes the caller its owner. Idempotent by sitekey, collision-safe, quota-limited; never publishes. ANONYMOUS callers (no sfmcp_ token) may also create: pass a valid captcha_token to mint a ghost-owned, publish-locked draft and receive a single-use claim credential \u2014 choose claim_method cookie_token (credential returned inline, default) or email_link (credential emailed to claim_email). A registered user later redeems the credential to adopt the site (or is auto-claimed on first registration); unclaimed drafts are garbage-collected after the retention deadline. THE DESIGN FLOW IS REQUIRED: the response carries design_flow \u2014 an interview you must relay VERBATIM to your user (never answer it yourself); store the answers via set_design_context and consult design_guidance before composing any page.","inputSchema":{"type":"object","additionalProperties":false,"required":["sitekey","label"],"properties":{"sitekey":{"type":"string","pattern":"^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$","maxLength":64,"description":"Globally-unique DNS-label machine key; also the idempotency anchor. Mirrors the static_site.sitekey entity constraint (validated server-side)."},"label":{"type":"string","minLength":1,"description":"Human-readable subsite title."},"languages":{"type":"array","items":{"type":"string"},"description":"Enabled content langcodes (each must be enabled on the platform). Defaults to [\u0027en\u0027]."},"template_id":{"type":"string","enum":["blank","starter"],"description":"Starter template for the new subsite. \u0027starter\u0027 (default) seeds the theme-agnostic layout component palette (container\/section\/divider\/two-column\/three-column) so the subsite is authorable immediately; \u0027blank\u0027 seeds nothing."},"libraries":{"type":"array","items":{"type":"string"},"maxItems":64,"description":"Offered library ids to attach with strategy \u0027packaged\u0027. Validated server-side against the live offered set (no static enum \u2014 the set changes as operators approve proposals); unknown\/un-offered ids fail validation with a message pointing at list_available_libraries and propose_libraries."},"propose_libraries":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["npm_package"],"properties":{"npm_package":{"type":"string","maxLength":214},"requested_range":{"type":"string"},"rationale":{"type":"string","maxLength":2000}}},"maxItems":8,"description":"Libraries NOT currently offered that the agent wants. The subsite is still created WITHOUT them; each entry becomes (or dedupes into) a persisted proposal for operator review, returned via warnings + proposal_ids."},"captcha_token":{"type":"string","description":"CAPTCHA \/ proof-of-work challenge solution. REQUIRED on the anonymous path (no sfmcp_ user token): an anonymous create mints a ghost-owned draft behind this challenge (FR-011). Ignored for authenticated callers."},"claim_method":{"type":"string","enum":["cookie_token","email_link"],"description":"How the anonymous caller will later reclaim the draft. \u0027cookie_token\u0027 (default) returns an opaque single-use credential in the response; \u0027email_link\u0027 emails the claim link to claim_email instead (the credential is NOT returned)."},"claim_email":{"type":"string","format":"email","description":"Destination address for the claim link. REQUIRED when claim_method is \u0027email_link\u0027; ignored otherwise."},"dry_run":{"type":"boolean","description":"When true, validate + plan only; write nothing."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string"},"id":{"type":["integer","string","null"],"description":"The affected entity id (reads correctly for every verb: create\/update\/delete\/skip)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id` (same value); prefer `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"published":{"type":"boolean"},"proposal_ids":{"type":"array","items":{"type":"string"},"description":"One proposal uuid per propose_libraries[] entry (deduped records included). The subsite is created without these libraries; each is registered for operator review."},"sitekey":{"type":"string","description":"Present only on the anonymous path: the sitekey of the created draft subsite."},"claim":{"type":"object","description":"Present only on the anonymous path: how to reclaim the ghost-owned draft. The draft is draft-locked (never publishable) until claimed by a registered user before retention_deadline.","properties":{"method":{"type":"string","enum":["cookie_token","email_link"]},"credential":{"type":"string","description":"Opaque single-use claim credential. Returned for method \u0027cookie_token\u0027 ONLY; for \u0027email_link\u0027 it is emailed, never returned."},"retention_deadline":{"type":["string","null"],"description":"ISO-8601 (UTC) instant after which the unclaimed draft is garbage-collected."},"claim_url":{"type":"string","description":"Where the human completes the claim after registering \/ logging in."}}},"plan":{"type":"array","description":"Present only on a dry_run: the classified create plan.","items":{"type":"object"}},"will_attach":{"type":"array","description":"Present only on a dry_run: the effective packaged attachment set (explicit libraries[] unioned with template-required libraries). Each entry: {id, source}.","items":{"type":"object","properties":{"id":{"type":"string"},"source":{"type":"string","description":"\u0027explicit\u0027 (a libraries[] id) or \u0027template\u0027 (implied by a template component\u0027s requires_libraries)."}}}},"would_propose":{"type":"array","description":"Present only on a dry_run: the npm packages that would be registered as proposals.","items":{"type":"object","properties":{"npm_package":{"type":"string"},"requested_range":{"type":"string"}}}},"design_flow":{"type":"object","description":"THE REQUIRED DESIGN FLOW. Relay design_flow.questions to your USER verbatim (never answer them yourself), compose their answers into DESIGN.md\/PRODUCT.md, store via set_design_context, then consult design_guidance before composing any page.","properties":{"required":{"type":"boolean"},"protocol":{"type":"string"},"questions":{"type":"array","items":{"type":"string"}}}}}},"path":"\/tools\/create_site","methods":["POST"]},{"name":"upload_media","description":"Uploads a base64-encoded image (JPEG\/PNG\/GIF\/WebP, max 5 MB) as subsite media and returns its browser URL for use in component image_url props.","inputSchema":{"type":"object","required":["sitekey","data_base64","alt"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of; the media is bound to it."},"data_base64":{"type":"string","minLength":1,"description":"The image bytes, base64-encoded. Must sniff as JPEG\/PNG\/GIF\/WebP; max 5 MB decoded. SVG is not accepted."},"alt":{"type":"string","minLength":1,"description":"Alt text for the image (required \u2014 accessibility is not optional)."},"filename":{"type":"string","description":"Suggested filename; sanitized, and its extension is replaced by the sniffed image type."},"label":{"type":"string","description":"Media label shown to operators; defaults to the sanitized filename."},"dry_run":{"type":"boolean","default":false,"description":"Validate the payload (decode, sniff, size, duplicate check) without storing anything."},"focal_x":{"type":"number","minimum":0,"maximum":100,"description":"Optional focal point x (0 left - 100 right, percentage). Set it whenever you know where the image\u0027s subject is \u2014 styled renditions crop around it. Supply both focal_x and focal_y or neither."},"focal_y":{"type":"number","minimum":0,"maximum":100,"description":"Optional focal point y (0 top - 100 bottom, percentage). If you are uncertain where the focal point of this new image belongs, ask your user to confirm instead of guessing; without one, renditions center-crop."},"alt_translations":{"type":"object","description":"Optional alt text per language, keyed by langcode (e.g. {\u0022nl\u0022: \u0022...\u0022, \u0022fr\u0022: \u0022...\u0022}). Any of the subsite\u0027s languages missing here gets a copy of `alt` instead, so the image is never published with an empty alt in a translated rendering.","additionalProperties":{"type":"string","minLength":1}}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"created, skipped (identical bytes already stored, or media surface unavailable), or dry_run."},"id":{"type":["integer","string","null"],"description":"The media entity id (prefer `id`)."},"created_id":{"type":["integer","string","null"],"description":"Back-compat alias of `id`."},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"},"description":"Non-fatal warnings \u2014 including dimension-fitness warnings when the source is smaller than a rendition target (act on those NOW: re-source a larger image while you still have the original at hand, instead of discovering stretched output at build time)."},"media":{"type":"object","description":"The stored (or matched) media summary. Use styled_urls.card \/ styled_urls.wide (root-RELATIVE paths, so static builds export the image) in component image props; focal_point is null until one is set (renditions then center-crop).","properties":{"id":{"type":"integer"},"label":{"type":"string"},"url":{"type":["string","null"]},"alt":{"type":"string"},"mime":{"type":["string","null"]},"size_bytes":{"type":["integer","null"]},"width":{"type":"integer"},"height":{"type":"integer"},"dimensions_assessment":{"type":"object","description":"Per rendition style (card\/wide): {target: \u0027WxH\u0027, sufficient: bool (source fills the target without upscaling), retina: bool (\u003E= 2x target, crisp on high-DPI), upscale_factor?: number (present when insufficient \u2014 how much the derivative stretches)}. If a style you intend to use is insufficient, source a larger image BEFORE placing it."}}},"plan":{"type":"array","description":"On status dry_run: what a real invocation would do.","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}},"focal_point":{"type":"object","description":"The stored focal point (relative percentages), present when focal_x\/focal_y were supplied.","properties":{"x":{"type":"integer"},"y":{"type":"integer"}}},"alt_languages":{"type":"object","description":"What happened per language: `translated` got text from alt_translations, `copied` got a copy of `alt`, `skipped` could not be written. `translatable` is false when media translation is not enabled on the platform, in which case the alt exists only in the source language.","properties":{"translated":{"type":"array","items":{"type":"string"}},"copied":{"type":"array","items":{"type":"string"}},"skipped":{"type":"array","items":{"type":"string"}},"translatable":{"type":"boolean"}}}}},"path":"\/tools\/upload_media","methods":["POST"]},{"name":"list_webforms","description":"Lists the subsite\u0027s webforms: element definitions, open\/closed status, snippet-embed state, and the ready-to-place embed markup (\u003Csiteforge-webform \u2026\u003E \u2014 put it in a page\u0027s HTML-capable prop; static builds bake it into a working form that submits back to the platform). Pair with create_webform and list_webform_submissions.","inputSchema":{"type":"object","required":["sitekey"],"properties":{"sitekey":{"type":"string","description":"The member subsite whose webforms to list."}},"additionalProperties":false},"outputSchema":{"type":"object","properties":{"webforms":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"title":{"type":"string"},"status":{"type":"string","description":"open | closed | scheduled."},"embed_enabled":{"type":"boolean","description":"Whether the static snippet embed is enabled for this form \u2014 required for it to work on built pages."},"embed_markup":{"type":"string","description":"Ready-to-place custom element; static builds bake it into a working form."},"definition":{"type":["object","null"],"description":"The form\u0027s element definition (fields, labels, types, required) as the snippet surface serves it."}}}},"warnings":{"type":"array","items":{"type":"string"}}}},"path":"\/tools\/list_webforms","methods":["POST"]},{"name":"set_menu_links","description":"REPLACES a subsite menu\u0027s entire link tree with the supplied list (nested children supported) \u2014 read list_menus first and send the complete intended tree, not a diff. Links: {title, url (site-relative \/path, full http(s), or \u0027\/\u0027 for home), weight?, enabled?, children?}. Changes appear on the next build_site.","inputSchema":{"type":"object","required":["sitekey","menu","links"],"additionalProperties":false,"properties":{"sitekey":{"type":"string","minLength":1,"description":"The subsite the caller is a member of."},"menu":{"type":"string","minLength":1,"description":"Short menu id from list_menus (e.g. main_nav, footer). The two STANDARD menus (main_nav, footer) are CREATED on first write when the subsite does not have them yet; other ids must already exist."},"links":{"type":"array","description":"The COMPLETE intended link tree \u2014 this REPLACES everything currently in the menu. Read list_menus first and send the full tree, not a diff.","items":{"type":"object","required":["title","url"],"properties":{"title":{"type":"string","minLength":1},"url":{"type":"string","minLength":1,"description":"Site-relative path (\/programma), full http(s) URL, or \u0027\/\u0027 for the homepage."},"weight":{"type":"integer","description":"Sort order; defaults to list position."},"enabled":{"type":"boolean","default":true},"children":{"type":"array","description":"Nested child links (same shape)."},"translations":{"type":"object","description":"Per-language link titles, langcode =\u003E translated title (e.g. {\u0022fr\u0022: \u0022Programme\u0022}). Stored as core entity translations; the rendered header\/footer show the current page language\u0027s title, falling back to the source title. Langcodes must be installed on the platform.","additionalProperties":{"type":"string"}}}}},"dry_run":{"type":"boolean","default":false,"description":"Report the replacement scope (deletes\/creates) without changing anything."}}},"outputSchema":{"type":"object","required":["status"],"properties":{"status":{"type":"string","description":"updated, skipped (menu surface unavailable), rolled_back, or dry_run."},"id":{"type":["integer","string","null"]},"created_id":{"type":["integer","string","null"]},"uuid":{"type":["string","null"]},"warnings":{"type":"array","items":{"type":"string"}},"menu":{"type":"string","description":"The replaced menu\u0027s short id (echoed)."},"deleted":{"type":"integer","description":"Links removed."},"created":{"type":"integer","description":"Links created (including nested children)."},"plan":{"type":"array","description":"On dry_run: the replacement scope.","items":{"type":"object","properties":{"op":{"type":"string"},"detail":{"type":"string"}}}}}},"path":"\/tools\/set_menu_links","methods":["POST"]}],"resources":[{"name":"design_quality","description":"The standing design-quality verdict for a subsite last build.","uri":"siteforge:\/\/subsite\/{sitekey}\/design-quality","mimeType":"application\/json"},{"name":"design_capabilities","description":"The design-quality providers and tiers available for a subsite, derived live at read time.","uri":"siteforge:\/\/subsite\/{sitekey}\/design-quality\/capabilities","mimeType":"application\/json"},{"name":"subsite_summary","description":"A compact summary of a SiteForge subsite (including its primary domain) for AI editing context.","uri":"siteforge:\/\/subsite\/{sitekey}\/summary","mimeType":"application\/json"},{"name":"subsite_capability_inventory","description":"The authoring capabilities available on a subsite, derived live from installed modules crossed with the caller scope.","uri":"siteforge:\/\/subsite\/{sitekey}\/capabilities","mimeType":"application\/json"}],"prompts":[{"name":"design_guidance","description":"Returns a design discipline instruction block (polish\/critique\/distill\/\u2026) with the subsite stored design context injected; your agent does the reasoning.","arguments":[{"name":"command","description":"The design discipline to apply (e.g. polish, critique, distill, typeset, harmonize).","required":true},{"name":"sitekey","description":"The subsite key whose PRODUCT.md\/DESIGN.md context is injected.","required":false},{"name":"target","description":"What to apply the guidance to.","required":false}]},{"name":"authoring_workflow_intro","description":"Primes an AI client with the recommended order for authoring a SiteForge subsite.","arguments":[{"name":"sitekey","description":"The subsite key being authored.","required":false}]},{"name":"editor_session_intro","description":"Primes an AI client with how to edit a SiteForge subsite.","arguments":[{"name":"sitekey","description":"The subsite key to introduce.","required":false}]},{"name":"design_site","description":"Primes an AI client to design a SiteForge subsite in a chosen mode, with the subsite design skill inlined.","arguments":[{"name":"mode","description":"The design pass to run: one of craft, shape, critique, audit, polish, typeset, layout, colorize.","required":true},{"name":"sitekey","description":"The subsite key to design.","required":true},{"name":"brief","description":"A short brief describing the design intent.","required":false},{"name":"target","description":"The specific target of the pass (e.g. a page, the hero).","required":false}]}],"oauth":{"authorization_server":"https:\/\/fedcanvas.be\/.well-known\/oauth-authorization-server","protected_resource":"https:\/\/fedcanvas.be\/.well-known\/oauth-protected-resource","scopes_supported":["captures:write","mcp:*","prompt:authoring_workflow_intro","prompt:design_guidance","prompt:design_site","prompt:editor_session_intro","resource:design_capabilities","resource:design_quality","resource:subsite_capability_inventory","resource:subsite_summary","tool:apply_site_template","tool:apply_theme_design_preset","tool:build_site","tool:capture_site_template","tool:check_design_quality","tool:clone_component","tool:create_canvas_page","tool:create_focal_point_session","tool:create_redirect","tool:create_site","tool:create_webform","tool:delete_canvas_page","tool:delete_redirect","tool:design_guidance","tool:generate_theme_design_from_prompt","tool:get_design_context","tool:get_focal_point_session","tool:get_platform_changelog","tool:get_skill","tool:get_subsite","tool:get_subsite_inventory","tool:get_subsite_urls","tool:get_theme_design_tokens","tool:get_theme_library","tool:import_media_from_url","tool:import_theme_design_dtcg","tool:ingest_local_findings","tool:list_automations","tool:list_available_components","tool:list_available_libraries","tool:list_canvas_pages","tool:list_library_proposals","tool:list_media","tool:list_menus","tool:list_redirects","tool:list_site_templates","tool:list_skills","tool:list_subsites","tool:list_subsite_pages","tool:list_subsite_setting_keys","tool:list_webforms","tool:list_webform_submissions","tool:propose_library","tool:read_canvas_page","tool:read_datagroup","tool:report_environment","tool:set_canvas_page_components","tool:set_design_context","tool:set_media_focal_point","tool:set_menu_links","tool:set_subsite_homepage","tool:set_subsite_setting","tool:set_theme_design_tokens","tool:set_theme_library","tool:suggest_alt_text","tool:test_ping","tool:translate_canvas_page","tool:update_canvas_page","tool:update_subsite_config","tool:upload_file","tool:upload_media"],"registration_endpoint":"https:\/\/fedcanvas.be\/oauth\/register"},"upload":{"session_endpoint":"https:\/\/fedcanvas.be\/siteforge\/upload\/session","max_bytes":104857600,"chunk_size":5242880,"accepted_types":[{"mime":"video\/mp4","extension":"mp4","kind":"video"},{"mime":"application\/pdf","extension":"pdf","kind":"document"},{"mime":"application\/msword","extension":"doc","kind":"document"},{"mime":"application\/vnd.openxmlformats-officedocument.wordprocessingml.document","extension":"docx","kind":"document"},{"mime":"application\/vnd.ms-excel","extension":"xls","kind":"document"},{"mime":"application\/vnd.openxmlformats-officedocument.spreadsheetml.sheet","extension":"xlsx","kind":"document"},{"mime":"application\/vnd.ms-powerpoint","extension":"ppt","kind":"document"},{"mime":"application\/vnd.openxmlformats-officedocument.presentationml.presentation","extension":"pptx","kind":"document"},{"mime":"application\/vnd.oasis.opendocument.text","extension":"odt","kind":"document"},{"mime":"application\/vnd.oasis.opendocument.spreadsheet","extension":"ods","kind":"document"},{"mime":"application\/vnd.oasis.opendocument.presentation","extension":"odp","kind":"document"},{"mime":"text\/plain","extension":"txt","kind":"document"},{"mime":"text\/csv","extension":"csv","kind":"document"}],"poster":{"max_bytes":8388608,"accepted_types":[{"mime":"image\/jpeg","extension":"jpg","kind":"image"},{"mime":"image\/png","extension":"png","kind":"image"},{"mime":"image\/webp","extension":"webp","kind":"image"}]}}}