From bcbc590da82307761396879479f756aee2686d8b Mon Sep 17 00:00:00 2001 From: jm-merchan Date: Fri, 12 Jun 2026 11:42:28 +0200 Subject: [PATCH] Add namespace override to KV tools --- pkg/tools/kv/delete_secret.go | 6 ++++++ pkg/tools/kv/list_secrets.go | 6 ++++++ pkg/tools/kv/namespace.go | 18 ++++++++++++++++++ pkg/tools/kv/read_secret.go | 6 ++++++ pkg/tools/kv/write_secret.go | 6 ++++++ 5 files changed, 42 insertions(+) create mode 100644 pkg/tools/kv/namespace.go diff --git a/pkg/tools/kv/delete_secret.go b/pkg/tools/kv/delete_secret.go index 4bcc40d..42f7926 100644 --- a/pkg/tools/kv/delete_secret.go +++ b/pkg/tools/kv/delete_secret.go @@ -38,6 +38,10 @@ func DeleteSecret(logger *log.Logger) server.ServerTool { mcp.DefaultString(""), mcp.Description("A optional key in the secret to delete. If not specified, all keys in the the secret will be deleted."), ), + mcp.WithString("namespace", + mcp.DefaultString(""), + mcp.Description("Optional Vault namespace override for this call (for example: 'admin/team-03'). If not set, uses the MCP session namespace."), + ), ), Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { return deleteSecretHandler(ctx, req, logger) @@ -69,6 +73,7 @@ func deleteSecretHandler(ctx context.Context, req mcp.CallToolRequest, logger *l if !ok { return mcp.NewToolResultError("Missing or invalid 'key' parameter"), nil } + namespace, _ := args["namespace"].(string) logger.WithFields(log.Fields{ "mount": mount, @@ -82,6 +87,7 @@ func deleteSecretHandler(ctx context.Context, req mcp.CallToolRequest, logger *l logger.WithError(err).Error("Failed to get Vault client") return mcp.NewToolResultError(fmt.Sprintf("Failed to get Vault client: %v", err)), nil } + vault = withOptionalNamespace(vault, namespace) mounts, err := vault.Sys().ListMounts() if err != nil { diff --git a/pkg/tools/kv/list_secrets.go b/pkg/tools/kv/list_secrets.go index 64b07c3..179591b 100644 --- a/pkg/tools/kv/list_secrets.go +++ b/pkg/tools/kv/list_secrets.go @@ -33,6 +33,10 @@ func ListSecrets(logger *log.Logger) server.ServerTool { mcp.WithString("path", mcp.DefaultString(""), mcp.Description("The full path to list the secrets to without the mount prefix. For example, if you want to list from 'secrets/application/credentials', this should be 'application/credentials'.")), + mcp.WithString("namespace", + mcp.DefaultString(""), + mcp.Description("Optional Vault namespace override for this call (for example: 'admin/team-03'). If not set, uses the MCP session namespace."), + ), ), Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { return listSecretsHandler(ctx, req, logger) @@ -58,6 +62,7 @@ func listSecretsHandler(ctx context.Context, req mcp.CallToolRequest, logger *lo if path == "" { path = "" } + namespace, _ := args["namespace"].(string) logger.WithFields(log.Fields{ "mount": mount, @@ -70,6 +75,7 @@ func listSecretsHandler(ctx context.Context, req mcp.CallToolRequest, logger *lo logger.WithError(err).Error("Failed to get Vault client") return mcp.NewToolResultError(fmt.Sprintf("Failed to get Vault client: %v", err)), nil } + vault = withOptionalNamespace(vault, namespace) // Construct the full path for listing fullPath := fmt.Sprintf(mount+"/%s", path) diff --git a/pkg/tools/kv/namespace.go b/pkg/tools/kv/namespace.go new file mode 100644 index 0000000..2d8bf11 --- /dev/null +++ b/pkg/tools/kv/namespace.go @@ -0,0 +1,18 @@ +// Copyright IBM Corp. 2025, 2026 +// SPDX-License-Identifier: MPL-2.0 + +package kv + +import ( + "strings" + + "github.com/hashicorp/vault/api" +) + +func withOptionalNamespace(vault *api.Client, namespace string) *api.Client { + ns := strings.TrimSpace(namespace) + if ns == "" { + return vault + } + return vault.WithNamespace(ns) +} \ No newline at end of file diff --git a/pkg/tools/kv/read_secret.go b/pkg/tools/kv/read_secret.go index 160acaf..f872baf 100644 --- a/pkg/tools/kv/read_secret.go +++ b/pkg/tools/kv/read_secret.go @@ -30,6 +30,10 @@ func ReadSecret(logger *log.Logger) server.ServerTool { mcp.Required(), mcp.Description("The full path to read the secret to without the mount prefix. For example, if you want to read from 'secrets/application/credentials', this should be 'application/credentials'."), ), + mcp.WithString("namespace", + mcp.DefaultString(""), + mcp.Description("Optional Vault namespace override for this call (for example: 'admin/team-03'). If not set, uses the MCP session namespace."), + ), ), Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { return readSecretHandler(ctx, req, logger) @@ -55,6 +59,7 @@ func readSecretHandler(ctx context.Context, req mcp.CallToolRequest, logger *log if !ok || path == "" { return mcp.NewToolResultError("Missing or invalid 'path' parameter"), nil } + namespace, _ := args["namespace"].(string) logger.WithFields(log.Fields{ "mount": mount, @@ -67,6 +72,7 @@ func readSecretHandler(ctx context.Context, req mcp.CallToolRequest, logger *log logger.WithError(err).Error("Failed to get Vault client") return mcp.NewToolResultError(fmt.Sprintf("Failed to get Vault client: %v", err)), nil } + vault = withOptionalNamespace(vault, namespace) mounts, err := vault.Sys().ListMounts() if err != nil { diff --git a/pkg/tools/kv/write_secret.go b/pkg/tools/kv/write_secret.go index 7c5bb34..2923eb6 100644 --- a/pkg/tools/kv/write_secret.go +++ b/pkg/tools/kv/write_secret.go @@ -43,6 +43,10 @@ func WriteSecret(logger *log.Logger) server.ServerTool { mcp.Required(), mcp.Description("The value to store the given key. For example if you want to write mysecret=myvalue, this should be 'myvalue'"), ), + mcp.WithString("namespace", + mcp.DefaultString(""), + mcp.Description("Optional Vault namespace override for this call (for example: 'admin/team-03'). If not set, uses the MCP session namespace."), + ), ), Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { return writeSecretHandler(ctx, req, logger) @@ -78,6 +82,7 @@ func writeSecretHandler(ctx context.Context, req mcp.CallToolRequest, logger *lo if !ok || value == "" { return mcp.NewToolResultError("Missing or invalid 'value' parameter"), nil } + namespace, _ := args["namespace"].(string) logger.WithFields(log.Fields{ "mount": mount, @@ -91,6 +96,7 @@ func writeSecretHandler(ctx context.Context, req mcp.CallToolRequest, logger *lo logger.WithError(err).Error("Failed to get Vault client") return mcp.NewToolResultError(fmt.Sprintf("Failed to get Vault client: %v", err)), nil } + vault = withOptionalNamespace(vault, namespace) mounts, err := vault.Sys().ListMounts() if err != nil {