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
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -262,12 +262,6 @@ Lists secrets in a KV mount under a specific path in Vault.
- `mount`: The mount path of the secret engine
- `path`: (Optional) The path to list secrets from (defaults to root)

#### delete_secret
Delete secrets (or keys) in a KV mount under a specific path in Vault.
- `mount`: The mount path of the secret engine
- `path`: The path to the secret to delete
- `key`: (Optional) The key name to delete from the entire secret (defaults to deleting the entire secret)

#### write_secret
Writes a secret to a KV mount in Vault.
- `mount`: The mount path of the secret engine
Comment on lines 263 to 267

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This README section is now inaccurate after the KV tool changes in this PR: write_secret no longer takes key/value and instead requires a data object, read_secret gained an optional version (KV v2 only), and several new KV tools were added/renamed. Please update the “Available Tools” docs to match the current tool schemas/names.

Copilot uses AI. Check for mistakes.
Expand Down
210 changes: 0 additions & 210 deletions pkg/tools/kv/delete_secret.go

This file was deleted.

148 changes: 148 additions & 0 deletions pkg/tools/kv/delete_secret_versions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// Copyright IBM Corp. 2025
// SPDX-License-Identifier: MPL-2.0

package kv

import (
"context"
"fmt"
"strings"

"github.com/hashicorp/vault-mcp-server/pkg/client"
"github.com/hashicorp/vault-mcp-server/pkg/utils"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
log "github.com/sirupsen/logrus"
)

// DeleteSecretVersions creates a tool for soft-deleting specific versions of a secret in a Vault KV v2 mount
func DeleteSecretVersions(logger *log.Logger) server.ServerTool {
return server.ServerTool{
Tool: mcp.NewTool("delete_secret_versions",
mcp.WithToolAnnotation(
mcp.ToolAnnotation{
DestructiveHint: utils.ToBoolPtr(true),
IdempotentHint: utils.ToBoolPtr(true),
},
),
mcp.WithDescription("Soft-delete specific versions of a secret in a KV v2 mount in Vault. The secret data is marked as deleted but can be recovered using undelete_secret. Only supported on KV v2 mounts."),
mcp.WithString("mount",
Comment on lines +28 to +29

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tool description references undelete_secret, but the tool added in this PR is undelete_secret_versions. Please update the wording to reference the correct tool name to avoid directing users to a non-existent tool.

Copilot uses AI. Check for mistakes.
mcp.Required(),
mcp.Description("The mount path of the secret engine."),
),
mcp.WithString("path",
mcp.Required(),
mcp.Description("The full path to the secret without the mount prefix."),
),
mcp.WithArray("versions",
mcp.Description("An array of version numbers to soft-delete. For example: [1, 3, 5]. If not specified, the latest version is soft-deleted. Soft-deleted versions can be recovered with undelete_secret."),
),
Comment on lines +37 to +39

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The versions parameter description says soft-deleted versions can be recovered with undelete_secret, but this PR introduces undelete_secret_versions. Update the description to point at the correct recovery tool name.

Copilot uses AI. Check for mistakes.
),
Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return deleteSecretVersionsHandler(ctx, req, logger)
},
}
}

func deleteSecretVersionsHandler(ctx context.Context, req mcp.CallToolRequest, logger *log.Logger) (*mcp.CallToolResult, error) {
logger.Debug("Handling delete_secret_versions request")

// Extract parameters
args, ok := req.Params.Arguments.(map[string]interface{})
if !ok {
return mcp.NewToolResultError("Missing or invalid arguments format"), nil
}

mount, err := utils.ExtractMountPath(args)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

path, ok := args["path"].(string)
if !ok || path == "" {
return mcp.NewToolResultError("Missing or invalid 'path' parameter"), nil
}

// Get Vault client from context
vault, err := client.GetVaultClientFromContext(ctx, logger)
if err != nil {
logger.WithError(err).Error("Failed to get Vault client")
return mcp.NewToolResultError(fmt.Sprintf("Failed to get Vault client: %v", err)), nil
}

isV2, err := getMountInfo(vault, mount)
if err != nil {
return mcp.NewToolResultError(err.Error()), nil
}

if !isV2 {
return mcp.NewToolResultError("delete_secret_versions is only supported on KV v2 mounts"), nil
}

versionsRaw, hasVersions := args["versions"].([]interface{})

if hasVersions && len(versionsRaw) > 0 {
// Convert float64 values from JSON to int
versions := make([]int, 0, len(versionsRaw))
for _, v := range versionsRaw {
vFloat, ok := v.(float64)
if !ok {
return mcp.NewToolResultError("Invalid version number in 'versions' array — each element must be a number"), nil
}
versions = append(versions, int(vFloat))
}

logger.WithFields(log.Fields{
"mount": mount,
"path": path,
"versions": versions,
}).Debug("Soft-deleting secret versions")

// Soft-delete specific versions at mount/delete/path
fullPath := fmt.Sprintf("%s/delete/%s", mount, strings.TrimPrefix(path, "/"))
_, err = vault.Logical().Write(fullPath, map[string]interface{}{
"versions": versions,
})
if err != nil {
logger.WithError(err).WithFields(log.Fields{
"mount": mount,
"path": path,
"full_path": fullPath,
"versions": versions,
}).Error("Failed to soft-delete secret versions")
return mcp.NewToolResultError(fmt.Sprintf("Failed to soft-delete secret versions: %v", err)), nil
}

logger.WithFields(log.Fields{
"mount": mount,
"path": path,
"versions": versions,
}).Info("Successfully soft-deleted secret versions")

return mcp.NewToolResultText(fmt.Sprintf("Successfully soft-deleted versions %v of secret at path '%s' in mount '%s'. Use undelete_secret to recover them.", versions, path, mount)), nil
}
Comment on lines +122 to +123

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Success message instructs users to run undelete_secret to recover versions, but that tool name doesn’t exist after this PR (it’s undelete_secret_versions). Please update the message so the CLI/agent guidance is actionable.

Copilot uses AI. Check for mistakes.

// No versions specified: DELETE on the data path soft-deletes the latest version
logger.WithFields(log.Fields{
"mount": mount,
"path": path,
}).Debug("Soft-deleting latest secret version")

dataPath := fmt.Sprintf("%s/data/%s", mount, strings.TrimPrefix(path, "/"))
_, err = vault.Logical().Delete(dataPath)
if err != nil {
logger.WithError(err).WithFields(log.Fields{
"mount": mount,
"path": path,
"full_path": dataPath,
}).Error("Failed to soft-delete latest secret version")
return mcp.NewToolResultError(fmt.Sprintf("Failed to soft-delete latest secret version: %v", err)), nil
}

logger.WithFields(log.Fields{
"mount": mount,
"path": path,
}).Info("Successfully soft-deleted latest secret version")

return mcp.NewToolResultText(fmt.Sprintf("Successfully soft-deleted the latest version of secret at path '%s' in mount '%s'. Use undelete_secret to recover it.", path, mount)), nil

Copilot AI Mar 9, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Success message for deleting the latest version references undelete_secret, but the tool exposed by this PR is undelete_secret_versions. Please update the guidance to the correct tool name.

Suggested change
return mcp.NewToolResultText(fmt.Sprintf("Successfully soft-deleted the latest version of secret at path '%s' in mount '%s'. Use undelete_secret to recover it.", path, mount)), nil
return mcp.NewToolResultText(fmt.Sprintf("Successfully soft-deleted the latest version of secret at path '%s' in mount '%s'. Use undelete_secret_versions to recover it.", path, mount)), nil

Copilot uses AI. Check for mistakes.
}
Loading
Loading