-
Notifications
You must be signed in to change notification settings - Fork 22
feat(sys): add auth method discovery and detail tools #87
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // Copyright IBM Corp. 2025 | ||
| // SPDX-License-Identifier: MPL-2.0 | ||
|
|
||
| package sys | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
|
|
||
| "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" | ||
| ) | ||
|
|
||
| type AuthMethod struct { | ||
| Path string `json:"path"` // Path where the auth method is mounted | ||
| Type string `json:"type"` // Type of the auth method (e.g., userpass, approle, oidc) | ||
| Description string `json:"description"` // Description of the auth method | ||
| Accessor string `json:"accessor"` // Unique accessor for the auth method | ||
| Local bool `json:"local"` // Whether the auth method is local to the namespace | ||
| SealWrap bool `json:"seal_wrap"` // Whether seal wrapping is enabled | ||
| ExternalEntropy bool `json:"external_entropy"` // Whether external entropy is used | ||
| DefaultLeaseTTL int `json:"default_lease_ttl"` // Default lease TTL | ||
| MaxLeaseTTL int `json:"max_lease_ttl"` // Max lease TTL | ||
| } | ||
|
|
||
| // ListAuthMethods creates a tool for listing Vault auth methods | ||
| func ListAuthMethods(logger *log.Logger) server.ServerTool { | ||
| return server.ServerTool{ | ||
| Tool: mcp.NewTool("list_auth_methods", | ||
| mcp.WithDescription("List all enabled authentication methods in Vault. Returns information about each auth method including type, path, and configuration."), | ||
| mcp.WithToolAnnotation( | ||
| mcp.ToolAnnotation{ | ||
| IdempotentHint: utils.ToBoolPtr(true), | ||
| ReadOnlyHint: utils.ToBoolPtr(true), | ||
| }, | ||
| ), | ||
| mcp.WithString("namespace", | ||
| mcp.DefaultString(""), | ||
| mcp.Description("Namespace path to list auth methods from (e.g., 'admin/' or empty for root). Defaults to current namespace.")), | ||
| ), | ||
| Handler: func(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { | ||
| return listAuthMethodsHandler(ctx, req, logger) | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| func listAuthMethodsHandler(ctx context.Context, req mcp.CallToolRequest, logger *log.Logger) (*mcp.CallToolResult, error) { | ||
| logger.Debug("Handling list_auth_methods request") | ||
|
|
||
| // Extract parameters | ||
| args, ok := req.Params.Arguments.(map[string]interface{}) | ||
| if !ok { | ||
| return mcp.NewToolResultError("Missing or invalid arguments format"), nil | ||
| } | ||
|
|
||
| namespace, _ := args["namespace"].(string) | ||
|
|
||
| logger.WithFields(log.Fields{ | ||
| "namespace": namespace, | ||
| }).Debug("Listing auth methods") | ||
|
|
||
| // 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 | ||
| } | ||
|
|
||
| // Create a new client instance with the specified namespace if provided | ||
| nsClient := vault | ||
| if namespace != "" { | ||
| nsClient = vault.WithNamespace(namespace) | ||
| logger.WithField("namespace", namespace).Debug("Using specified namespace") | ||
| } | ||
|
|
||
| // List auth methods from Vault | ||
| auths, err := nsClient.Sys().ListAuth() | ||
| if err != nil { | ||
| logger.WithError(err).Error("Failed to list auth methods") | ||
| return mcp.NewToolResultError(fmt.Sprintf("Failed to list auth methods: %v", err)), nil | ||
| } | ||
|
|
||
| var results []*AuthMethod | ||
| for path, auth := range auths { | ||
| method := &AuthMethod{ | ||
| Path: path, | ||
| Type: auth.Type, | ||
| Description: auth.Description, | ||
| Accessor: auth.Accessor, | ||
| Local: auth.Local, | ||
| SealWrap: auth.SealWrap, | ||
| ExternalEntropy: auth.ExternalEntropyAccess, | ||
| DefaultLeaseTTL: auth.Config.DefaultLeaseTTL, | ||
| MaxLeaseTTL: auth.Config.MaxLeaseTTL, | ||
| } | ||
| results = append(results, method) | ||
| } | ||
|
|
||
| // Marshal the struct to JSON | ||
| jsonData, err := json.Marshal(results) | ||
| if err != nil { | ||
| logger.WithError(err).Error("Failed to marshal auth methods to JSON") | ||
| return mcp.NewToolResultError(fmt.Sprintf("Error marshaling JSON: %v", err)), nil | ||
| } | ||
|
|
||
| logger.WithField("auth_method_count", len(results)).Debug("Successfully listed auth methods") | ||
| return mcp.NewToolResultText(string(jsonData)), nil | ||
| } | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,152 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // Copyright IBM Corp. 2025 | ||||||||||||||||||||||||||||||||||||||||||||||||||
| // SPDX-License-Identifier: MPL-2.0 | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| package sys | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "context" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "encoding/json" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| "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" | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| type AuthMethodDetail struct { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Path string `json:"path"` // Path where the auth method is mounted | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Type string `json:"type"` // Type of the auth method | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Description string `json:"description"` // Description of the auth method | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Accessor string `json:"accessor"` // Unique accessor for the auth method | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Local bool `json:"local"` // Whether the auth method is local | ||||||||||||||||||||||||||||||||||||||||||||||||||
| SealWrap bool `json:"seal_wrap"` // Whether seal wrapping is enabled | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ExternalEntropy bool `json:"external_entropy"` // Whether external entropy is used | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Config map[string]any `json:"config,omitempty"` // Full configuration details | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Options map[string]any `json:"options,omitempty"` // Auth method options | ||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| // ReadAuthMethod creates a tool for reading details of a specific auth method | ||||||||||||||||||||||||||||||||||||||||||||||||||
| func ReadAuthMethod(logger *log.Logger) server.ServerTool { | ||||||||||||||||||||||||||||||||||||||||||||||||||
| return server.ServerTool{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| Tool: mcp.NewTool("read_auth_method", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.WithDescription("Read detailed configuration and information about a specific authentication method in Vault. Returns full details including config, options, and metadata."), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.WithToolAnnotation( | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.ToolAnnotation{ | ||||||||||||||||||||||||||||||||||||||||||||||||||
| IdempotentHint: utils.ToBoolPtr(true), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ReadOnlyHint: utils.ToBoolPtr(true), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.WithString("path", | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.Required(), | ||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.Description("The mount path of the auth method to read (e.g., 'approle/', 'userpass/', 'oidc/'). Include trailing slash.")), | ||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||
| mcp.Description("The mount path of the auth method to read (e.g., 'approle/', 'userpass/', 'oidc/'). Include trailing slash.")), | |
| mcp.Description("The mount path of the auth method to read (e.g., 'approle/', 'userpass/', 'oidc/'). Trailing slash is optional; it will be added automatically if missing.")), |
Copilot
AI
Mar 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config block is only included when DefaultLeaseTTL or MaxLeaseTTL is > 0, but other config fields (e.g., force_no_cache, headers, token_type, etc.) can be set even when TTLs are 0. This means read_auth_method can return an empty config despite the tool description claiming it returns full config details. Consider populating result.Config unconditionally (or gating on the presence of any non-zero/non-empty config field), and keep the tool description aligned with the actual output.
| // Include full config details | |
| if authMethod.Config.DefaultLeaseTTL > 0 || authMethod.Config.MaxLeaseTTL > 0 { | |
| result.Config = map[string]any{ | |
| "default_lease_ttl": authMethod.Config.DefaultLeaseTTL, | |
| "max_lease_ttl": authMethod.Config.MaxLeaseTTL, | |
| "force_no_cache": authMethod.Config.ForceNoCache, | |
| "token_type": authMethod.Config.TokenType, | |
| "audit_non_hmac_request_keys": authMethod.Config.AuditNonHMACRequestKeys, | |
| "audit_non_hmac_response_keys": authMethod.Config.AuditNonHMACResponseKeys, | |
| "listing_visibility": authMethod.Config.ListingVisibility, | |
| "passthrough_request_headers": authMethod.Config.PassthroughRequestHeaders, | |
| "allowed_response_headers": authMethod.Config.AllowedResponseHeaders, | |
| } | |
| // Include full config details (independent of TTL values) | |
| result.Config = map[string]any{ | |
| "default_lease_ttl": authMethod.Config.DefaultLeaseTTL, | |
| "max_lease_ttl": authMethod.Config.MaxLeaseTTL, | |
| "force_no_cache": authMethod.Config.ForceNoCache, | |
| "token_type": authMethod.Config.TokenType, | |
| "audit_non_hmac_request_keys": authMethod.Config.AuditNonHMACRequestKeys, | |
| "audit_non_hmac_response_keys": authMethod.Config.AuditNonHMACResponseKeys, | |
| "listing_visibility": authMethod.Config.ListingVisibility, | |
| "passthrough_request_headers": authMethod.Config.PassthroughRequestHeaders, | |
| "allowed_response_headers": authMethod.Config.AllowedResponseHeaders, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
list_auth_methodsdeclares only an optionalnamespace, but the handler unconditionally type-assertsreq.Params.Argumentsto a map and errors if it’s nil. A CallToolRequest can omitArgumentsentirely (nil), so this tool will fail when invoked with no parameters. Consider treating nil arguments as an empty map and only parsingnamespacewhen arguments are provided (similar to other sys tools that allow optional params).