diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index ed429b188..c9c35d7e5 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -29,19 +29,25 @@ func NewServer() *MCPServer { mcpServer.AddTool( mcp.NewTool("create", - mcp.WithDescription("Creates a knative function in the current directory"), - mcp.WithString("name", - mcp.Required(), - mcp.Description("Name of the function to be created"), - ), - mcp.WithString("language", - mcp.Required(), - mcp.Description("Language/Runtime of the function to be created"), - ), + mcp.WithDescription("Creates a Knative function project in the current or specified directory"), mcp.WithString("cwd", mcp.Required(), mcp.Description("Current working directory of the MCP client"), ), + mcp.WithString("name", + mcp.Required(), + mcp.Description("Name of the function to be created (used as subdirectory)"), + ), + mcp.WithString("language", + mcp.Required(), + mcp.Description("Language runtime to use (e.g., node, go, python)"), + ), + + // Optional flags + mcp.WithString("template", mcp.Description("Function template (e.g., http, cloudevents)")), + mcp.WithString("repository", mcp.Description("URI to Git repo containing the template")), + mcp.WithBoolean("confirm", mcp.Description("Prompt to confirm options interactively")), + mcp.WithBoolean("verbose", mcp.Description("Print verbose logs")), ), handleCreateTool, ) @@ -51,7 +57,7 @@ func NewServer() *MCPServer { mcp.WithDescription("Deploys the function to the cluster"), mcp.WithString("registry", mcp.Required(), - mcp.Description("Name of the registry to be used to push the function image"), + mcp.Description("Registry to be used to push the function image"), ), mcp.WithString("cwd", mcp.Required(), @@ -61,17 +67,41 @@ func NewServer() *MCPServer { mcp.Required(), mcp.Description("Builder to be used to build the function image"), ), - mcp.WithBoolean("remote", - mcp.DefaultBool(false), - mcp.Description("If true, the function will be deployed remotely"), - ), + + // Optional flags + mcp.WithString("image", mcp.Description("Full image name (overrides registry)")), + mcp.WithString("namespace", mcp.Description("Namespace to deploy the function into")), + mcp.WithString("git-url", mcp.Description("Git URL containing the function source")), + mcp.WithString("git-branch", mcp.Description("Git branch for remote deployment")), + mcp.WithString("git-dir", mcp.Description("Directory inside the Git repository")), + mcp.WithString("builder-image", mcp.Description("Custom builder image")), + mcp.WithString("domain", mcp.Description("Domain for the function route")), + mcp.WithString("platform", mcp.Description("Target platform to build for (e.g., linux/amd64)")), + mcp.WithString("path", mcp.Description("Path to the function directory")), + mcp.WithString("build", mcp.Description(`Build control: "true", "false", or "auto"`)), + mcp.WithString("pvc-size", mcp.Description("Custom volume size for remote builds")), + mcp.WithString("service-account", mcp.Description("Kubernetes ServiceAccount to use")), + mcp.WithString("remote-storage-class", mcp.Description("Storage class for remote volume")), + + mcp.WithBoolean("confirm", mcp.Description("Prompt for confirmation before deploying")), + mcp.WithBoolean("push", mcp.Description("Push image to registry before deployment")), + mcp.WithBoolean("verbose", mcp.Description("Print verbose logs")), + mcp.WithBoolean("registry-insecure", mcp.Description("Skip TLS verification for registry")), + mcp.WithBoolean("build-timestamp", mcp.Description("Use actual time in image metadata")), + mcp.WithBoolean("remote", mcp.Description("Trigger remote deployment")), ), handleDeployTool, ) mcpServer.AddTool( mcp.NewTool("list", - mcp.WithDescription("Lists all the functions deployed in the cluster"), + mcp.WithDescription("Lists all deployed functions in the current or specified namespace"), + + // Optional flags + mcp.WithBoolean("all-namespaces", mcp.Description("List functions in all namespaces (overrides --namespace)")), + mcp.WithString("namespace", mcp.Description("The namespace to list functions in (default is current/active)")), + mcp.WithString("output", mcp.Description("Output format: human, plain, json, xml, yaml")), + mcp.WithBoolean("verbose", mcp.Description("Enable verbose output")), ), handleListTool, ) @@ -85,12 +115,24 @@ func NewServer() *MCPServer { ), mcp.WithString("builder", mcp.Required(), - mcp.Description("Builder to be used to build the function image"), + mcp.Description("Builder to be used to build the function image (pack, s2i, host)"), ), mcp.WithString("registry", mcp.Required(), - mcp.Description("Name of the registry to be used to push the function image"), + mcp.Description("Registry to be used to push the function image (e.g. ghcr.io/user)"), ), + + // Optional flags + mcp.WithString("builder-image", mcp.Description("Custom builder image to use with buildpacks")), + mcp.WithString("image", mcp.Description("Full image name (overrides registry + function name)")), + mcp.WithString("path", mcp.Description("Path to the function directory (default is current dir)")), + mcp.WithString("platform", mcp.Description("Target platform, e.g. linux/amd64 (for s2i builds)")), + + mcp.WithBoolean("confirm", mcp.Description("Prompt for confirmation before proceeding")), + mcp.WithBoolean("push", mcp.Description("Push image to registry after building")), + mcp.WithBoolean("verbose", mcp.Description("Enable verbose logging output")), + mcp.WithBoolean("registry-insecure", mcp.Description("Skip TLS verification for insecure registries")), + mcp.WithBoolean("build-timestamp", mcp.Description("Use actual time for image timestamp (buildpacks only)")), ), handleBuildTool, ) @@ -102,6 +144,14 @@ func NewServer() *MCPServer { mcp.Required(), mcp.Description("Name of the function to be deleted"), ), + + // Optional flags + mcp.WithString("namespace", mcp.Description("Namespace to delete from (default: current or active)")), + mcp.WithString("path", mcp.Description("Path to the function project (default is current directory)")), + mcp.WithString("all", mcp.Description(`Delete all related resources like Pipelines, Secrets ("true"/"false")`)), + + mcp.WithBoolean("confirm", mcp.Description("Prompt to confirm before deletion")), + mcp.WithBoolean("verbose", mcp.Description("Enable verbose output")), ), handleDeleteTool, ) @@ -185,12 +235,33 @@ func handleCreateTool( return mcp.NewToolResultError(err.Error()), nil } - cmd := exec.Command("func", "create", "-l", language, name) - cmd.Dir = cwd - out, err := cmd.Output() - if err != nil { - return mcp.NewToolResultError(err.Error()), nil + args := []string{"create", "-l", language} + + // Optional flags + if v := request.GetString("template", ""); v != "" { + args = append(args, "--template", v) } + if v := request.GetString("repository", ""); v != "" { + args = append(args, "--repository", v) + } + if request.GetBool("confirm", false) { + args = append(args, "--confirm") + } + if request.GetBool("verbose", false) { + args = append(args, "--verbose") + } + + // `name` is passed as a positional argument (directory to create in) + args = append(args, name) + + cmd := exec.Command("func", args...) + cmd.Dir = cwd + + out, err := cmd.CombinedOutput() + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("func create failed: %s", out)), nil + } + body := []byte(fmt.Sprintf(`{"result": "%s"}`, out)) return mcp.NewToolResultText(string(body)), nil } @@ -211,20 +282,74 @@ func handleDeployTool( if err != nil { return mcp.NewToolResultError(err.Error()), nil } - remote, err := request.RequireBool("remote") - if err != nil { - return mcp.NewToolResultError(err.Error()), nil + + args := []string{"deploy", "--builder", builder, "--registry", registry} + + // Optional flags + if v := request.GetString("image", ""); v != "" { + args = append(args, "--image", v) } - var cmd *exec.Cmd - if remote { - cmd = exec.Command("func", "deploy", "--registry", registry, "--builder", builder, "--remote") - } else { - cmd = exec.Command("func", "deploy", "--registry", registry, "--builder", builder) + if v := request.GetString("namespace", ""); v != "" { + args = append(args, "--namespace", v) } + if v := request.GetString("git-url", ""); v != "" { + args = append(args, "--git-url", v) + } + if v := request.GetString("git-branch", ""); v != "" { + args = append(args, "--git-branch", v) + } + if v := request.GetString("git-dir", ""); v != "" { + args = append(args, "--git-dir", v) + } + if v := request.GetString("builder-image", ""); v != "" { + args = append(args, "--builder-image", v) + } + if v := request.GetString("domain", ""); v != "" { + args = append(args, "--domain", v) + } + if v := request.GetString("platform", ""); v != "" { + args = append(args, "--platform", v) + } + if v := request.GetString("path", ""); v != "" { + args = append(args, "--path", v) + } + if v := request.GetString("build", ""); v != "" { + args = append(args, "--build", v) + } + if v := request.GetString("pvc-size", ""); v != "" { + args = append(args, "--pvc-size", v) + } + if v := request.GetString("service-account", ""); v != "" { + args = append(args, "--service-account", v) + } + if v := request.GetString("remote-storage-class", ""); v != "" { + args = append(args, "--remote-storage-class", v) + } + + if request.GetBool("confirm", false) { + args = append(args, "--confirm") + } + if request.GetBool("push", false) { + args = append(args, "--push") + } + if request.GetBool("verbose", false) { + args = append(args, "--verbose") + } + if request.GetBool("registry-insecure", false) { + args = append(args, "--registry-insecure") + } + if request.GetBool("build-timestamp", false) { + args = append(args, "--build-timestamp") + } + if request.GetBool("remote", false) { + args = append(args, "--remote") + } + + cmd := exec.Command("func", args...) cmd.Dir = cwd - out, err := cmd.Output() + out, err := cmd.CombinedOutput() if err != nil { - return mcp.NewToolResultError(err.Error()), nil + return mcp.NewToolResultError(fmt.Sprintf("func deploy failed: %s", out)), nil } body := []byte(fmt.Sprintf(`{"result": "%s"}`, out)) return mcp.NewToolResultText(string(body)), nil @@ -234,10 +359,26 @@ func handleListTool( ctx context.Context, request mcp.CallToolRequest, ) (*mcp.CallToolResult, error) { - cmd := exec.Command("func", "list") - out, err := cmd.Output() + args := []string{"list"} + + // Optional flags + if request.GetBool("all-namespaces", false) { + args = append(args, "--all-namespaces") + } + if v := request.GetString("namespace", ""); v != "" { + args = append(args, "--namespace", v) + } + if v := request.GetString("output", ""); v != "" { + args = append(args, "--output", v) + } + if request.GetBool("verbose", false) { + args = append(args, "--verbose") + } + + cmd := exec.Command("func", args...) + out, err := cmd.CombinedOutput() if err != nil { - return mcp.NewToolResultError(err.Error()), nil + return mcp.NewToolResultError(fmt.Sprintf("func list failed: %s", out)), nil } body := []byte(fmt.Sprintf(`{"result": "%s"}`, out)) return mcp.NewToolResultText(string(body)), nil @@ -260,11 +401,43 @@ func handleBuildTool( return mcp.NewToolResultError(err.Error()), nil } - cmd := exec.Command("func", "build", "--builder", builder, "--registry", registry) + args := []string{"build", "--builder", builder, "--registry", registry} + + // Optional flags + if v := request.GetString("builder-image", ""); v != "" { + args = append(args, "--builder-image", v) + } + if v := request.GetString("image", ""); v != "" { + args = append(args, "--image", v) + } + if v := request.GetString("path", ""); v != "" { + args = append(args, "--path", v) + } + if v := request.GetString("platform", ""); v != "" { + args = append(args, "--platform", v) + } + + if v := request.GetBool("confirm", false); v { + args = append(args, "--confirm") + } + if v := request.GetBool("push", false); v { + args = append(args, "--push") + } + if v := request.GetBool("verbose", false); v { + args = append(args, "--verbose") + } + if v := request.GetBool("registry-insecure", false); v { + args = append(args, "--registry-insecure") + } + if v := request.GetBool("build-timestamp", false); v { + args = append(args, "--build-timestamp") + } + + cmd := exec.Command("func", args...) cmd.Dir = cwd - out, err := cmd.Output() + out, err := cmd.CombinedOutput() if err != nil { - return mcp.NewToolResultError(err.Error()), nil + return mcp.NewToolResultError(fmt.Sprintf("func build failed: %s", out)), nil } body := []byte(fmt.Sprintf(`{"result": "%s"}`, out)) return mcp.NewToolResultText(string(body)), nil @@ -278,11 +451,33 @@ func handleDeleteTool( if err != nil { return mcp.NewToolResultError(err.Error()), nil } - cmd := exec.Command("func", "delete", name) - out, err := cmd.Output() - if err != nil { - return mcp.NewToolResultError(err.Error()), nil + + args := []string{"delete", name} + + // Optional flags + if v := request.GetString("namespace", ""); v != "" { + args = append(args, "--namespace", v) } + if v := request.GetString("path", ""); v != "" { + args = append(args, "--path", v) + } + if v := request.GetString("all", ""); v != "" { + args = append(args, "--all", v) + } + + if request.GetBool("confirm", false) { + args = append(args, "--confirm") + } + if request.GetBool("verbose", false) { + args = append(args, "--verbose") + } + + cmd := exec.Command("func", args...) + out, err := cmd.CombinedOutput() + if err != nil { + return mcp.NewToolResultError(fmt.Sprintf("func delete failed: %s", out)), nil + } + body := []byte(fmt.Sprintf(`{"result": "%s"}`, out)) return mcp.NewToolResultText(string(body)), nil }