From c5daac3366e57a503c096f6ee71ec8ba3351e0e8 Mon Sep 17 00:00:00 2001 From: logikonline Date: Sun, 5 Apr 2026 12:49:19 -0400 Subject: [PATCH] feat(mcp): add stats, value props, and CTA tools for landing pages Implements three new MCP tools for landing page management: update_landing_stats for stat counters, update_landing_value_props for value proposition cards, and update_landing_cta for bottom call-to-action section. Each tool supports structured data with validation and integrates with existing config save flow. --- routers/api/v2/mcp.go | 6 ++ routers/api/v2/mcp_pages.go | 128 ++++++++++++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/routers/api/v2/mcp.go b/routers/api/v2/mcp.go index 373456af06..277779b0a8 100644 --- a/routers/api/v2/mcp.go +++ b/routers/api/v2/mcp.go @@ -783,6 +783,12 @@ func handleToolsCall(ctx *context_service.APIContext, req *MCPRequest) { result, err = toolUpdateLandingSEO(ctx, params.Arguments) case "update_landing_theme": result, err = toolUpdateLandingTheme(ctx, params.Arguments) + case "update_landing_stats": + result, err = toolUpdateLandingStats(ctx, params.Arguments) + case "update_landing_value_props": + result, err = toolUpdateLandingValueProps(ctx, params.Arguments) + case "update_landing_cta": + result, err = toolUpdateLandingCTA(ctx, params.Arguments) default: sendMCPError(ctx, req.ID, -32602, "Unknown tool", params.Name) return diff --git a/routers/api/v2/mcp_pages.go b/routers/api/v2/mcp_pages.go index f7b47afa0d..11b587276c 100644 --- a/routers/api/v2/mcp_pages.go +++ b/routers/api/v2/mcp_pages.go @@ -244,6 +244,69 @@ var mcpPagesTools = []MCPTool{ }, }, }, + { + Name: "update_landing_stats", + Description: "Update the stats counters displayed on the landing page. Each stat has a value and label.", + InputSchema: map[string]any{ + "type": "object", + "required": []string{"owner", "repo", "stats"}, + "properties": map[string]any{ + "owner": map[string]any{"type": "string", "description": "Repository owner"}, + "repo": map[string]any{"type": "string", "description": "Repository name"}, + "stats": map[string]any{ + "type": "array", + "items": map[string]any{ + "type": "object", + "properties": map[string]any{ + "value": map[string]any{"type": "string"}, + "label": map[string]any{"type": "string"}, + }, + }, + "description": "Array of stat counters (e.g., [{value: '15+', label: 'Tools'}])", + }, + }, + }, + }, + { + Name: "update_landing_value_props", + Description: "Update the value propositions section. Each value prop has a title, description, and icon.", + InputSchema: map[string]any{ + "type": "object", + "required": []string{"owner", "repo", "value_props"}, + "properties": map[string]any{ + "owner": map[string]any{"type": "string", "description": "Repository owner"}, + "repo": map[string]any{"type": "string", "description": "Repository name"}, + "value_props": map[string]any{ + "type": "array", + "items": map[string]any{ + "type": "object", + "properties": map[string]any{ + "title": map[string]any{"type": "string"}, + "description": map[string]any{"type": "string"}, + "icon": map[string]any{"type": "string"}, + }, + }, + "description": "Array of value propositions", + }, + }, + }, + }, + { + Name: "update_landing_cta", + Description: "Update the call-to-action section at the bottom of the page with headline, subheadline, and button.", + InputSchema: map[string]any{ + "type": "object", + "required": []string{"owner", "repo"}, + "properties": map[string]any{ + "owner": map[string]any{"type": "string", "description": "Repository owner"}, + "repo": map[string]any{"type": "string", "description": "Repository name"}, + "headline": map[string]any{"type": "string", "description": "CTA headline"}, + "subheadline": map[string]any{"type": "string", "description": "CTA subheadline"}, + "button_label": map[string]any{"type": "string", "description": "Button text"}, + "button_url": map[string]any{"type": "string", "description": "Button URL"}, + }, + }, + }, } // ── Tool Implementations ────────────────────────────────── @@ -573,6 +636,71 @@ func toolUpdateLandingTheme(ctx *context.APIContext, args map[string]any) (any, return saveAndReturn(ctx, repoObj, config, "theme") } +func toolUpdateLandingStats(ctx *context.APIContext, args map[string]any) (any, error) { + config, repoObj, err := getConfigForUpdate(ctx, args) + if err != nil { + return nil, err + } + + if stats, ok := args["stats"].([]any); ok { + config.Stats = nil + for _, s := range stats { + if sm, ok := s.(map[string]any); ok { + config.Stats = append(config.Stats, pages_module.StatConfig{ + Value: strVal(sm, "value"), + Label: strVal(sm, "label"), + }) + } + } + } + + return saveAndReturn(ctx, repoObj, config, "stats") +} + +func toolUpdateLandingValueProps(ctx *context.APIContext, args map[string]any) (any, error) { + config, repoObj, err := getConfigForUpdate(ctx, args) + if err != nil { + return nil, err + } + + if vps, ok := args["value_props"].([]any); ok { + config.ValueProps = nil + for _, v := range vps { + if vm, ok := v.(map[string]any); ok { + config.ValueProps = append(config.ValueProps, pages_module.ValuePropConfig{ + Title: strVal(vm, "title"), + Description: strVal(vm, "description"), + Icon: strVal(vm, "icon"), + }) + } + } + } + + return saveAndReturn(ctx, repoObj, config, "value_props") +} + +func toolUpdateLandingCTA(ctx *context.APIContext, args map[string]any) (any, error) { + config, repoObj, err := getConfigForUpdate(ctx, args) + if err != nil { + return nil, err + } + + if v, ok := args["headline"].(string); ok { + config.CTASection.Headline = v + } + if v, ok := args["subheadline"].(string); ok { + config.CTASection.Subheadline = v + } + if v, ok := args["button_label"].(string); ok { + config.CTASection.Button.Label = v + } + if v, ok := args["button_url"].(string); ok { + config.CTASection.Button.URL = v + } + + return saveAndReturn(ctx, repoObj, config, "cta_section") +} + // ── Helpers ────────────────────────────────── func getConfigForUpdate(ctx *context.APIContext, args map[string]any) (*pages_module.LandingConfig, *repo_model.Repository, error) {