Private/Test-ToolParameter.ps1

function Test-ToolParameter {
    <#
    .SYNOPSIS
        Validates a parameter hashtable against a tool's JSON inputSchema.

    .DESCRIPTION
        Checks required parameters, unknown parameters, primitive type
        compatibility, and enum / ValidateSet constraints. Throws an
        ArgumentException listing all violations if the parameters do not
        conform to the schema. The caller is expected to let the exception
        bubble up so ConvertTo-JsonRpcResponse can map it to JSON-RPC error
        code -32602 (Invalid params).
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        $Schema,

        [hashtable]$Parameters = @{}
    )

    $errors = [System.Collections.Generic.List[string]]::new()
    $properties = $Schema.properties
    $required = @()
    if ($Schema.Contains('required')) {
        $required = @($Schema.required)
    }

    # Required parameters must be present
    foreach ($name in $required) {
        if (-not $Parameters.ContainsKey($name)) {
            $errors.Add("Missing required parameter '$name'.")
        }
    }

    foreach ($key in @($Parameters.Keys)) {
        if (-not $properties -or -not $properties.Contains($key)) {
            $errors.Add("Unknown parameter '$key' is not defined in the tool schema.")
            continue
        }

        $propSchema = $properties[$key]
        $value = $Parameters[$key]
        $expectedType = $propSchema.type

        if ($null -eq $value) {
            if ($key -in $required) {
                $errors.Add("Parameter '$key' cannot be null.")
            }
            continue
        }

        $typeValid = switch ($expectedType) {
            'string'  { $value -is [string] }
            'integer' { ($value -is [int] -or $value -is [long] -or $value -is [byte] -or $value -is [int16]) -and $value -isnot [bool] }
            'number'  { ($value -is [int] -or $value -is [long] -or $value -is [double] -or $value -is [decimal] -or $value -is [single]) -and $value -isnot [bool] }
            'boolean' { $value -is [bool] }
            'array'   { $value -is [array] -or $value -is [System.Collections.IList] }
            'object'  { $value -is [hashtable] -or $value -is [System.Collections.IDictionary] -or $value -is [pscustomobject] }
            default   { $true }
        }
        if (-not $typeValid) {
            $actual = $value.GetType().Name
            $errors.Add("Parameter '$key' expected type '$expectedType' but received '$actual'.")
            continue
        }

        # Enum / ValidateSet constraint on a scalar
        if ($propSchema.enum -and $expectedType -ne 'array') {
            if ($value -notin $propSchema.enum) {
                $allowed = ($propSchema.enum -join "', '")
                $errors.Add("Parameter '$key' value '$value' is not one of the allowed values: '$allowed'.")
            }
        }

        # Enum constraint on array items
        if ($expectedType -eq 'array' -and $propSchema.items -and $propSchema.items.enum) {
            foreach ($item in $value) {
                if ($item -notin $propSchema.items.enum) {
                    $allowed = ($propSchema.items.enum -join "', '")
                    $errors.Add("Parameter '$key' contains invalid item '$item'; allowed item values: '$allowed'.")
                }
            }
        }
    }

    if ($errors.Count -gt 0) {
        $message = "Invalid parameters: " + ($errors -join ' ')
        throw [System.ArgumentException]::new($message)
    }
}