Skip to main content
Glama

Keeper Secrets Manager - MCP

notation.go5.95 kB
package ksm import ( "fmt" "regexp" "strconv" "strings" "github.com/keeper-security/ksm-mcp/pkg/types" ) var ( // Notation patterns arrayPattern = regexp.MustCompile(`^(.+)\[(\d+)\]$`) propPattern = regexp.MustCompile(`^(.+)\[([a-zA-Z_]\w*)\]$`) nestedPattern = regexp.MustCompile(`^(.+)\[(\d+)\]\[([a-zA-Z_]\w*)\]$`) ) // ParseNotation parses KSM notation into structured format func ParseNotation(notation string) (*types.NotationResult, error) { if notation == "" { return nil, fmt.Errorf("notation cannot be empty") } parts := strings.Split(notation, "/") if len(parts) < 2 { return nil, fmt.Errorf("invalid notation format: expected at least 2 parts separated by '/'") } result := &types.NotationResult{ Index: -1, // Initialize to -1 (no index) } // First part is either UID or Title first := parts[0] if isValidUID(first) { result.UID = first } else { result.Title = first } // Parse the rest based on the second part switch parts[1] { case "field": if len(parts) < 3 { return nil, fmt.Errorf("field notation requires field name") } return parseFieldNotation(result, parts[2]) case "custom_field": if len(parts) < 3 { return nil, fmt.Errorf("custom_field notation requires field name") } result.Custom = true return parseFieldNotation(result, parts[2]) case "file": if len(parts) < 3 { return nil, fmt.Errorf("file notation requires filename") } result.File = parts[2] return result, nil default: return nil, fmt.Errorf("unknown notation type: %s", parts[1]) } } // parseFieldNotation parses field notation including array/property access func parseFieldNotation(result *types.NotationResult, fieldPart string) (*types.NotationResult, error) { // Check if field is empty if fieldPart == "" { return nil, fmt.Errorf("field name cannot be empty") } // Check for nested array with property: field[0][property] if matches := nestedPattern.FindStringSubmatch(fieldPart); matches != nil { result.Field = matches[1] index, err := strconv.Atoi(matches[2]) if err != nil { return nil, fmt.Errorf("invalid array index: %s", matches[2]) } result.Index = index result.Property = matches[3] return result, nil } // Check for array access: field[0] if matches := arrayPattern.FindStringSubmatch(fieldPart); matches != nil { result.Field = matches[1] index, err := strconv.Atoi(matches[2]) if err != nil { return nil, fmt.Errorf("invalid array index: %s", matches[2]) } result.Index = index return result, nil } // Check for property access: field[property] if matches := propPattern.FindStringSubmatch(fieldPart); matches != nil { result.Field = matches[1] result.Property = matches[2] return result, nil } // Simple field result.Field = fieldPart return result, nil } // BuildNotation builds a notation string from components func BuildNotation(result *types.NotationResult) string { var parts []string // First part: UID or Title if result.UID != "" { parts = append(parts, result.UID) } else if result.Title != "" { parts = append(parts, result.Title) } else { return "" } // Second part: type if result.File != "" { parts = append(parts, "file", result.File) return strings.Join(parts, "/") } if result.Custom { parts = append(parts, "custom_field") } else { parts = append(parts, "field") } // Third part: field with optional array/property field := result.Field if result.Property != "" && result.Index >= 0 { // Nested: field[0][property] field = fmt.Sprintf("%s[%d][%s]", field, result.Index, result.Property) } else if result.Index >= 0 { // Array: field[0] field = fmt.Sprintf("%s[%d]", field, result.Index) } else if result.Property != "" { // Property: field[property] field = fmt.Sprintf("%s[%s]", field, result.Property) } parts = append(parts, field) return strings.Join(parts, "/") } // ValidateNotation validates a notation string func ValidateNotation(notation string) error { _, err := ParseNotation(notation) return err } // isValidUID checks if a string is a valid UID format func isValidUID(s string) bool { if len(s) < 16 || len(s) > 32 { return false } // UIDs typically contain alphanumeric, underscore, and hyphen for _, r := range s { if !((r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' || r == '-') { return false } } return true } // ExtractFieldPath extracts the field path from notation for SDK usage func ExtractFieldPath(notation string) (string, error) { parsed, err := ParseNotation(notation) if err != nil { return "", err } // Build path for SDK var path string if parsed.Custom { path = "custom_field/" } else { path = "field/" } path += parsed.Field // Add array/property access if needed if parsed.Property != "" && parsed.Index >= 0 { path += fmt.Sprintf("[%d][%s]", parsed.Index, parsed.Property) } else if parsed.Index >= 0 { path += fmt.Sprintf("[%d]", parsed.Index) } else if parsed.Property != "" { path += fmt.Sprintf("[%s]", parsed.Property) } return path, nil } // IsFileNotation checks if notation refers to a file func IsFileNotation(notation string) bool { parts := strings.Split(notation, "/") return len(parts) >= 2 && parts[1] == "file" } // IsCustomFieldNotation checks if notation refers to a custom field func IsCustomFieldNotation(notation string) bool { parts := strings.Split(notation, "/") return len(parts) >= 2 && parts[1] == "custom_field" } // SplitNotationParts splits notation into its component parts func SplitNotationParts(notation string) (recordRef string, fieldType string, fieldPath string, err error) { parts := strings.Split(notation, "/") if len(parts) < 3 { return "", "", "", fmt.Errorf("invalid notation: expected at least 3 parts") } recordRef = parts[0] fieldType = parts[1] fieldPath = strings.Join(parts[2:], "/") return recordRef, fieldType, fieldPath, nil }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Keeper-Security/keeper-mcp-golang-docker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server