package seo import ( "net/http" apicontrollers "git.golem15.com/golem15/sm-seo-plugin/controllers/api" "git.golem15.com/golem15/summercms/modules/pact" ) var _ pact.HasMCP = (*Plugin)(nil) // mcpGuard is the guard every SEO tool runs as: the backend account linked // to the MCP user. The routes resolve that administrator themselves. const mcpGuard = "backend" // mcpPermission is the one SEO permission (D-15) every tool is listed with; // the routes check it on every call. const mcpPermission = apicontrollers.PermissionManage // MCP declares the SEO tools (D-10, P-24): one per operation of the SEO // admin (operations.go maps each admin operation to its tool), each bound // to a /_seo/api/v1 management route, run as the backend account linked to // the MCP user and listed only when that account has golem15.seo.manage. // pier prefixes them with seo_. GET tools are read-only, so a read grant // sees only those; deletes and "mark as gone" are destructive. func (p *Plugin) MCP() pact.MCP { destructive := true return pact.MCP{Tools: []pact.MCPTool{ // Pages addressed by URL (D-11, D-13). { Name: "get_url_seo", Title: "Get the SEO of a URL", Description: "Shows the effective SEO of a page addressed by URL: every value with its source (URL rule, record, template, global settings, content or default), the redirect decision, the owner a change would be written to (the record or a URL rule) and the warnings of the SEO tab.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/seo", Input: apicontrollers.GetSEOInput{}, Permissions: []string{mcpPermission}, }, { Name: "set_url_seo", Title: "Set the SEO of a URL", Description: "Changes the SEO values of a page addressed by URL. A record behind the URL gets the change in its SEO tab in the URL's locale; otherwise, or with force_rule, an exact URL rule is written. The answer says where it was written.", Guard: mcpGuard, Method: http.MethodPut, Path: "/_seo/api/v1/seo", Input: apicontrollers.SetSEOInput{}, Permissions: []string{mcpPermission}, }, // URL rules (D-02). { Name: "list_rules", Title: "List URL rules", Description: "Lists the URL rules (SEO for an address with or without a record behind it) by address, with a search text and the match kind.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/rules", Input: apicontrollers.ListRulesInput{}, Permissions: []string{mcpPermission}, }, { Name: "show_rule", Title: "Show a URL rule", Description: "Shows one URL rule by id.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/rules/{id}", Input: apicontrollers.RuleIDInput{}, Permissions: []string{mcpPermission}, }, { Name: "create_rule", Title: "Create a URL rule", Description: "Creates a URL rule: an exact address or a pattern where * matches any text, with the values it sets. A rule wins over the record's SEO tab.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/rules", Input: apicontrollers.CreateRuleInput{}, Permissions: []string{mcpPermission}, }, { Name: "update_rule", Title: "Update a URL rule", Description: "Changes the fields sent of one URL rule by id.", Guard: mcpGuard, Method: http.MethodPut, Path: "/_seo/api/v1/rules/{id}", Input: apicontrollers.UpdateRuleInput{}, Permissions: []string{mcpPermission}, }, { Name: "delete_rule", Title: "Delete a URL rule", Description: "Deletes one URL rule by id; history keeps its values.", Guard: mcpGuard, Method: http.MethodDelete, Path: "/_seo/api/v1/rules/{id}", Input: apicontrollers.RuleIDInput{}, Permissions: []string{mcpPermission}, Destructive: &destructive, }, // Redirects (D-16 to D-19). { Name: "list_redirects", Title: "List redirects", Description: "Lists redirects, newest change first, with a search text and the status (301, 302, 410) and origin filters.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/redirects", Input: apicontrollers.ListRedirectsInput{}, Permissions: []string{mcpPermission}, }, { Name: "show_redirect", Title: "Show a redirect", Description: "Shows one redirect by id.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/redirects/{id}", Input: apicontrollers.RedirectIDInput{}, Permissions: []string{mcpPermission}, }, { Name: "create_redirect", Title: "Create a redirect", Description: "Creates a 301, 302 or 410 redirect from a path, a pattern or a regular expression. A redirect that would create a loop is refused with redirect_loop and the loop's path; a chain is saved with a warning.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/redirects", Input: apicontrollers.CreateRedirectInput{}, Permissions: []string{mcpPermission}, }, { Name: "update_redirect", Title: "Update a redirect", Description: "Changes the fields sent of one redirect by id; a loop is refused like a create.", Guard: mcpGuard, Method: http.MethodPut, Path: "/_seo/api/v1/redirects/{id}", Input: apicontrollers.UpdateRedirectInput{}, Permissions: []string{mcpPermission}, }, { Name: "delete_redirect", Title: "Delete a redirect", Description: "Deletes one redirect by id; history keeps its values.", Guard: mcpGuard, Method: http.MethodDelete, Path: "/_seo/api/v1/redirects/{id}", Input: apicontrollers.RedirectIDInput{}, Permissions: []string{mcpPermission}, Destructive: &destructive, }, // 404 log (D-20). { Name: "list_not_found", Title: "List the 404 log", Description: "Lists the addresses that answered 404, most hits first, with whether a redirect answers them now (status open or resolved).", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/notfound", Input: apicontrollers.ListNotFoundInput{}, Permissions: []string{mcpPermission}, }, { Name: "redirect_not_found", Title: "Redirect a 404 address", Description: "Creates a 301 from a logged 404 address to a target. Refused when a rule already answers the address.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/notfound/{id}/redirect", Input: apicontrollers.RedirectNotFoundInput{}, Permissions: []string{mcpPermission}, }, { Name: "gone_not_found", Title: "Mark a 404 address as gone", Description: "Creates a 410 for a logged 404 address, which tells search engines to drop it. Refused when a rule already answers the address.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/notfound/{id}/gone", Input: apicontrollers.NotFoundIDInput{}, Permissions: []string{mcpPermission}, Destructive: &destructive, }, { Name: "delete_not_found", Title: "Remove a 404 log entry", Description: "Removes one entry from the 404 log; it comes back on its next 404.", Guard: mcpGuard, Method: http.MethodDelete, Path: "/_seo/api/v1/notfound/{id}", Input: apicontrollers.NotFoundIDInput{}, Permissions: []string{mcpPermission}, Destructive: &destructive, }, // robots.txt and AI bots (D-22, D-24, D-25, D-99). { Name: "show_robots", Title: "Show robots.txt", Description: "Shows the robots.txt rules, the AI bot switches, the file crawlers get in production and whether the site runs outside production (crawlers then get Disallow: /).", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/robots", Input: struct{}{}, Permissions: []string{mcpPermission}, }, { Name: "update_robots", Title: "Update robots.txt", Description: "Changes the robots.txt rules and the AI bot switches sent. The answer lists noindex addresses the new rules keep Googlebot from.", Guard: mcpGuard, Method: http.MethodPut, Path: "/_seo/api/v1/robots", Input: apicontrollers.UpdateRobotsInput{}, Permissions: []string{mcpPermission}, }, { Name: "test_robots", Title: "Test an address against robots.txt", Description: "Answers whether a crawler may fetch an address under the saved production robots.txt, with Google's matcher and the deciding line.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/robots/test", Input: apicontrollers.TestRobotsInput{}, Permissions: []string{mcpPermission}, }, // Settings (D-03, D-05, D-07, D-20b, D-21, D-23, D-104). { Name: "show_settings", Title: "Show the SEO settings", Description: "Shows the SEO settings (templates, defaults, the query parameter list, indexing paths, the address form, verification) and the settings of every content type.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/settings", Input: struct{}{}, Permissions: []string{mcpPermission}, }, { Name: "update_settings", Title: "Update the SEO settings", Description: "Changes the SEO settings sent and, under types, the settings of content types by key, with the checks of the settings screen.", Guard: mcpGuard, Method: http.MethodPut, Path: "/_seo/api/v1/settings", Input: apicontrollers.UpdateSettingsInput{}, Permissions: []string{mcpPermission}, }, // History (D-13). { Name: "list_history", Title: "List SEO history", Description: "Lists SEO changes, newest first, with author, time, via (admin, mcp, import, auto, undo), before and after values, filtered by via, kind or batch.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/history", Input: apicontrollers.ListHistoryInput{}, Permissions: []string{mcpPermission}, }, { Name: "show_history", Title: "Show an SEO change", Description: "Shows one SEO change by id with its before and after values.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/history/{id}", Input: apicontrollers.HistoryIDInput{}, Permissions: []string{mcpPermission}, }, // Undo (D-13). { Name: "undo_change", Title: "Undo an SEO change", Description: "Restores the values before one SEO change from history; the undo is itself recorded. Refused with undo_stale when a later change touched the same values (naming who and when), undo_loop when the restore would create a redirect loop, undo_gone when the record is gone.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/history/{id}/undo", Input: apicontrollers.HistoryIDInput{}, Permissions: []string{mcpPermission}, }, { Name: "undo_batch", Title: "Undo a batch", Description: "Undoes every change of a batch or an import (by batch id) as a whole, or none of them, with the refusals of undo_change.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/history/batches/{batch}/undo", Input: apicontrollers.HistoryBatchInput{}, Permissions: []string{mcpPermission}, }, // Bulk changes and CSV (D-12, D-13). { Name: "apply_batch", Title: "Apply a batch of SEO changes", Description: "Applies a list of changes (set_seo by URL, redirect and rule creates, updates and deletes) all or nothing. With dry_run it answers the diff and the conflicts (redirect loops and chains, noindex on a published page, a canonical pointing to a redirected page) and writes nothing; a loop refuses the whole batch.", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/batch", Input: apicontrollers.ApplyBatchInput{}, Permissions: []string{mcpPermission}, }, { Name: "export_csv", Title: "Export SEO data as CSV", Description: "Exports redirects, URL rules or the SEO of records (by URL) as CSV, UTF-8 with a BOM, 1000 rows per page; cells that start a formula are prefixed with an apostrophe.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/export", Input: apicontrollers.ExportInput{}, Permissions: []string{mcpPermission}, }, { Name: "import_csv", Title: "Import SEO data from CSV", Description: "Imports redirects, URL rules or the SEO of records (rows address pages by URL; no record means a URL rule) from CSV of at most 900 KB. With dry_run it previews every row as new, changed, unchanged or refused with notes; otherwise every row is applied as one batch, or nothing while a row is refused (a loop, an invalid value).", Guard: mcpGuard, Method: http.MethodPost, Path: "/_seo/api/v1/import", Input: apicontrollers.ImportInput{}, Permissions: []string{mcpPermission}, }, // Audit (D-13, D-14 CMS part). { Name: "list_issues", Title: "List SEO issues", Description: "Lists the CMS-data SEO issues: missing and duplicate titles and descriptions, URL rules overriding a record, redirect chains, canonicals pointing to noindex or redirected addresses, and noindex addresses robots.txt blocks; each names the tool that fixes it.", Guard: mcpGuard, Method: http.MethodGet, Path: "/_seo/api/v1/issues", Input: apicontrollers.ListIssuesInput{}, Permissions: []string{mcpPermission}, }, }} }