workflows/default/systems/mcp/core-helpers.psm1
|
# Core Helper Functions # Essential utilities for all MCP tools #region Solution Discovery function Find-SolutionRoot { <# .SYNOPSIS Walks up directory tree to find .bot directory #> param( [string]$StartPath = $PWD.Path ) $current = Get-Item -Path $StartPath -ErrorAction SilentlyContinue while ($current) { $botPath = Join-Path $current.FullName '.bot' if (Test-Path $botPath -PathType Container) { return $current.FullName } $current = $current.Parent } return $null } #endregion #region Error Codes # Core error codes (used across all tools) $script:CoreErrorCodes = @{ DOTBOT_NOT_FOUND = "DOTBOT_NOT_FOUND" INVALID_PARAMETER = "INVALID_PARAMETER" INVALID_ARGUMENTS = "INVALID_ARGUMENTS" IO_ERROR = "IO_ERROR" } #endregion #region Envelope Response function New-ErrorObject { <# .SYNOPSIS Creates a structured error object #> param( [Parameter(Mandatory)] [string]$Code, [Parameter(Mandatory)] [string]$Message, [string]$Path = $null, [hashtable]$Details = $null ) $error = @{ code = $Code message = $Message } if ($Path) { $error.path = $Path } if ($Details) { $error.details = $Details } return $error } function Start-ToolTimer { <# .SYNOPSIS Starts a stopwatch for timing tool execution #> return [System.Diagnostics.Stopwatch]::StartNew() } function Get-ToolDuration { <# .SYNOPSIS Gets elapsed milliseconds from stopwatch #> param( [Parameter(Mandatory)] [System.Diagnostics.Stopwatch]$Stopwatch ) return [int]$Stopwatch.ElapsedMilliseconds } function Get-McpHost { <# .SYNOPSIS Detects the MCP host environment #> # Detect MCP host from environment if ($env:WARP_SESSION) { return "warp" } if ($env:CLAUDE_DESKTOP) { return "claude-desktop" } if ($env:CI) { return "ci" } return $null } function New-EnvelopeResponse { <# .SYNOPSIS Creates a standardized envelope response for MCP tools #> param( [Parameter(Mandatory)] [string]$Tool, [Parameter(Mandatory)] [string]$Version, [Parameter(Mandatory)] [string]$Summary, [Parameter(Mandatory)] [hashtable]$Data, [array]$Warnings = @(), [array]$Errors = @(), [hashtable]$Intent = $null, [array]$Actions = $null, [Parameter(Mandatory)] [string]$Source, [Parameter(Mandatory)] [int]$DurationMs, [string]$Host = $null, [string]$CorrelationId = $null, [string]$WriteTo = $null ) # Auto-compute status based on errors and warnings $status = if ($Errors.Count -gt 0) { "error" } elseif ($Warnings.Count -gt 0) { "warning" } else { "ok" } $response = @{ schema_id = "dotbot-mcp-response@1" tool = $Tool version = $Version status = $status summary = $Summary data = $Data warnings = $Warnings errors = $Errors audit = @{ timestamp = (Get-Date).ToUniversalTime().ToString('o') duration_ms = $DurationMs source = $Source } } if ($Host) { $response.audit.host = $Host } if ($CorrelationId) { $response.audit.correlation_id = $CorrelationId } if ($WriteTo) { $response.audit.write_to = $WriteTo } if ($Intent) { $response.intent = $Intent } if ($Actions) { $response.actions = $Actions } return $response } function Assert-EnvelopeSchema { <# .SYNOPSIS Validates envelope response structure #> param( [Parameter(Mandatory)] [hashtable]$Response ) # Basic validation $required = @('schema_id', 'tool', 'version', 'status', 'summary', 'data', 'warnings', 'errors', 'audit') foreach ($field in $required) { if (-not $Response.ContainsKey($field)) { throw "Missing required field: $field" } } if ($Response.status -notin @('ok', 'warning', 'error')) { throw "Invalid status: $($Response.status)" } # Validate audit required fields $auditRequired = @('timestamp', 'duration_ms', 'source') foreach ($field in $auditRequired) { if (-not $Response.audit.ContainsKey($field)) { throw "Missing required audit field: $field" } } return $true } #endregion # Export all functions Export-ModuleMember -Function @( 'Find-SolutionRoot', 'New-ErrorObject', 'Start-ToolTimer', 'Get-ToolDuration', 'Get-McpHost', 'New-EnvelopeResponse', 'Assert-EnvelopeSchema' ) # Export error codes Export-ModuleMember -Variable @('CoreErrorCodes') |