Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 74 additions & 8 deletions pkg/mcp/tools_config_envs.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,21 @@ func (s *Server) configEnvsListHandler(ctx context.Context, r *mcp.CallToolReque
// config_envs_add

var configEnvsAddTool = &mcp.Tool{
Name: "config_envs_add",
Title: "Config Environment Variables Add",
Description: "Adds an environment variable to a function's configuration.",
Name: "config_envs_add",
Title: "Config Environment Variables Add",
Description: `Adds an environment variable to a function's configuration.

Supports six source types:
1. Literal value: set Name and Value directly.
2. Local env var: set Name and Value to "{{ env:LOCAL_VAR }}".
3. Secret (all keys): set SecretName only — imports every key from the Secret as env vars.
4. Secret (one key): set Name, SecretName, and SecretKey.
5. ConfigMap (all): set ConfigMapName only — imports every key from the ConfigMap as env vars.
6. ConfigMap (one key): set Name, ConfigMapName, and ConfigMapKey.
Comment thread
Ankitsinghsisodya marked this conversation as resolved.

When SecretName or ConfigMapName is provided, the tool constructs the
appropriate "{{ secret:… }}" or "{{ configMap:… }}" value template automatically.
Value is mutually exclusive with SecretName and ConfigMapName.`,
Annotations: &mcp.ToolAnnotations{
Title: "Config Environment Variables Add",
ReadOnlyHint: false,
Expand All @@ -60,16 +72,67 @@ var configEnvsAddTool = &mcp.Tool{
}

type ConfigEnvsAddInput struct {
Path string `json:"path" jsonschema:"required,Path to the function project directory"`
Name *string `json:"name,omitempty" jsonschema:"Name of the environment variable"`
Value *string `json:"value,omitempty" jsonschema:"Value of the environment variable"`
Verbose *bool `json:"verbose,omitempty" jsonschema:"Enable verbose logging output"`
Path string `json:"path" jsonschema:"required,Path to the function project directory"`
Name *string `json:"name,omitempty" jsonschema:"Name of the environment variable"`
Value *string `json:"value,omitempty" jsonschema:"Literal value or template expression (e.g. '{{ env:MY_VAR }}') for the environment variable"`
SecretName *string `json:"secretName,omitempty" jsonschema:"Name of the Kubernetes Secret to source the value from"`
SecretKey *string `json:"secretKey,omitempty" jsonschema:"Key within the Secret; omit to import all keys as env vars"`
ConfigMapName *string `json:"configMapName,omitempty" jsonschema:"Name of the Kubernetes ConfigMap to source the value from"`
ConfigMapKey *string `json:"configMapKey,omitempty" jsonschema:"Key within the ConfigMap; omit to import all keys as env vars"`
Verbose *bool `json:"verbose,omitempty" jsonschema:"Enable verbose logging output"`
Comment thread
Ankitsinghsisodya marked this conversation as resolved.
}

// validate returns an error for any illegal input combination before args are
// constructed and forwarded to the CLI. Character-set validation is deferred to
// func's own parser, keeping this layer as a thin pass-through.
func (i ConfigEnvsAddInput) validate() error {
if i.Value != nil && (i.SecretName != nil || i.ConfigMapName != nil) {
return fmt.Errorf("value is mutually exclusive with secretName/configMapName; provide one or the other")
}
if i.SecretKey != nil && i.SecretName == nil {
return fmt.Errorf("secretKey requires secretName to be set")
}
if i.ConfigMapKey != nil && i.ConfigMapName == nil {
return fmt.Errorf("configMapKey requires configMapName to be set")
}
if i.SecretName != nil && i.ConfigMapName != nil {
return fmt.Errorf("secretName and configMapName are mutually exclusive; provide only one source")
}
// All-keys import (no secretKey/configMapKey) is incompatible with --name: the
// underlying env validation only allows whole-secret/configMap templates when name is nil.
if i.SecretName != nil && i.SecretKey == nil && i.Name != nil {
return fmt.Errorf("name must not be set when importing all keys from a Secret (omit secretKey to import all keys)")
}
if i.ConfigMapName != nil && i.ConfigMapKey == nil && i.Name != nil {
return fmt.Errorf("name must not be set when importing all keys from a ConfigMap (omit configMapKey to import all keys)")
}
return nil
}

func (i ConfigEnvsAddInput) Args() []string {
args := []string{"envs", "add", "--path", i.Path}
args = appendStringFlag(args, "--name", i.Name)
args = appendStringFlag(args, "--value", i.Value)

if i.Value != nil {
args = appendStringFlag(args, "--value", i.Value)
} else if i.SecretName != nil {
var v string
if i.SecretKey != nil {
v = fmt.Sprintf("{{ secret:%s:%s }}", *i.SecretName, *i.SecretKey)
} else {
v = fmt.Sprintf("{{ secret:%s }}", *i.SecretName)
}
args = append(args, "--value", v)
Comment thread
Ankitsinghsisodya marked this conversation as resolved.
} else if i.ConfigMapName != nil {
var v string
if i.ConfigMapKey != nil {
v = fmt.Sprintf("{{ configMap:%s:%s }}", *i.ConfigMapName, *i.ConfigMapKey)
} else {
v = fmt.Sprintf("{{ configMap:%s }}", *i.ConfigMapName)
}
args = append(args, "--value", v)
}

args = appendBoolFlag(args, "--verbose", i.Verbose)
return args
}
Expand All @@ -79,6 +142,9 @@ type ConfigEnvsAddOutput struct {
}

func (s *Server) configEnvsAddHandler(ctx context.Context, r *mcp.CallToolRequest, input ConfigEnvsAddInput) (result *mcp.CallToolResult, output ConfigEnvsAddOutput, err error) {
if err = input.validate(); err != nil {
return
}
out, err := s.executor.Execute(ctx, "config", input.Args()...)
if err != nil {
err = fmt.Errorf("%w\n%s", err, string(out))
Expand Down
Loading
Loading