feat: page registry with its admin screen and signpost type

- golem15_seo_pages (migration 202610280002): the site's own frontend pages
  with an address and a name per locale, kind, origin, parent, content date
  and note; settings routes_reported_at
- SEO > Pages (golem15.seo.pages) after URL rules: list with kind, origin
  and missing filters and the missing row state, form with the P9 partial;
  the plugin's form extension saves the address and name per locale and
  refuses url_required, url_invalid, url_taken and url_owned
- menu item type seo-page with a content descriptor, so each page gets
  the SEO tab, resolves ok and is listed in the sitemaps grouped by kind
  (pages, offers, services, landing-pages, titled per locale)
- management API GET/POST /_seo/api/v1/pages, PUT/DELETE /pages/{id} and
  the MCP tools seo_list_pages, seo_create_page, seo_update_page,
  seo_delete_page (in this commit because the operations parity test maps
  every Pages operation to its tool)
This commit is contained in:
Jakub Zych
2026-11-04 13:20:45 +01:00
parent 17b810dfde
commit 9e802ec850
27 changed files with 2281 additions and 9 deletions

43
mcp.go
View File

@@ -154,6 +154,49 @@ func (p *Plugin) MCP() pact.MCP {
Destructive: &destructive,
},
// The page registry (D-33).
{
Name: "list_pages",
Title: "List registry pages",
Description: "Lists the page registry (the site's own frontend pages such as the home page, contact or offer pages) by address, with every locale's address and name, the kind, the origin (manual, reported by the site, MCP) and whether the site's newest route report missed the page. Filters: search text, id, kind, origin, missing.",
Guard: mcpGuard,
Method: http.MethodGet,
Path: "/_seo/api/v1/pages",
Input: apicontrollers.ListPagesInput{},
Permissions: []string{mcpPermission},
},
{
Name: "create_page",
Title: "Create a registry page",
Description: "Adds a frontend page to the registry, so it gets SEO values (set them with set_url_seo on its address) and a place in the sitemaps: its path per locale, name, kind (page, offer, service, landing), parent and content date. Refused for an address another page or another content type (a post, a category) already has.",
Guard: mcpGuard,
Method: http.MethodPost,
Path: "/_seo/api/v1/pages",
Input: apicontrollers.CreatePageInput{},
Permissions: []string{mcpPermission},
},
{
Name: "update_page",
Title: "Update a registry page",
Description: "Changes the fields sent of one registry page by id; in url and title only the locales sent. A changed address gets a 301 from the old one.",
Guard: mcpGuard,
Method: http.MethodPut,
Path: "/_seo/api/v1/pages/{id}",
Input: apicontrollers.UpdatePageInput{},
Permissions: []string{mcpPermission},
},
{
Name: "delete_page",
Title: "Delete a registry page",
Description: "Deletes one registry page by id with its SEO values; its address leaves the sitemaps on the next rebuild. A page the site reports comes back with its next route report.",
Guard: mcpGuard,
Method: http.MethodDelete,
Path: "/_seo/api/v1/pages/{id}",
Input: apicontrollers.PageIDInput{},
Permissions: []string{mcpPermission},
Destructive: &destructive,
},
// 404 log (D-20).
{
Name: "list_not_found",