Private/Start-SqlMcpSidecar.ps1
|
function Start-SqlMcpSidecar { <# .SYNOPSIS Builds the SQL MCP sidecar image (if needed), creates a Docker network, and starts the sidecar container. .DESCRIPTION The sidecar runs an MCP server that holds SQL Server connection strings and enforces read-only access. The main Claude container connects to it over the Docker network — the connection strings never enter the Claude container. Returns [PSCustomObject]@{ NetworkName; SidecarName; McpUrl }, or $null on failure. #> [CmdletBinding()] param( [Parameter(Mandatory)] [System.Security.SecureString[]]$SqlConnections, [Parameter(Mandatory)] [string]$NetworkName, [Parameter(Mandatory)] [version]$ModuleVersion, [Parameter(Mandatory)] [ValidateSet('linux', 'windows')] [string]$ContainerOS ) $imageTag = "dclaude-sql-mcp-${ContainerOS}:v$ModuleVersion" $sidecarName = "sql-mcp-$($NetworkName -replace '^dclaude-net-', '')" # Build the sidecar image if it doesn't already exist. $existing = docker image inspect $imageTag 2>$null if (-not $existing) { # sidecars/ is a sibling of Private/ in the installed module layout. # In the dev repo it lives at the repo root; try both locations. $moduleRoot = Split-Path $PSScriptRoot $sidecarDir = Join-Path $moduleRoot 'sidecars/sql-mcp' if (-not (Test-Path (Join-Path $sidecarDir 'Dockerfile'))) { $repoRoot = Split-Path (Split-Path $moduleRoot) $sidecarDir = Join-Path $repoRoot 'sidecars/sql-mcp' } $dockerfile = if ($ContainerOS -eq 'windows') { 'Dockerfile.windows' } else { 'Dockerfile' } $dockerfileFull = Join-Path $sidecarDir $dockerfile if (-not (Test-Path $dockerfileFull)) { Write-Error "SQL MCP sidecar Dockerfile not found. Expected at: $dockerfileFull" return } Write-Host '[dclaude] Building SQL MCP sidecar image...' -ForegroundColor DarkGray docker build -t $imageTag -f $dockerfileFull -q $sidecarDir 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "Failed to build SQL MCP sidecar image." return } } # Create the Docker network. docker network create $NetworkName 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "Failed to create Docker network '$NetworkName'." return } # Build the sidecar docker run args with decrypted connection strings. $sidecarArgs = @( 'run', '-d', '--rm' '--name', $sidecarName '--network', $NetworkName '--network-alias', 'sql-mcp' ) for ($i = 0; $i -lt $SqlConnections.Count; $i++) { $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($SqlConnections[$i]) try { $plain = [System.Runtime.InteropServices.Marshal]::PtrToStringBSTR($bstr) } finally { [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr) } $n = $i + 1 $sidecarArgs += '-e' $sidecarArgs += "SQL_CONN_${n}=${plain}" } $sidecarArgs += $imageTag docker @sidecarArgs 2>&1 | Out-Null if ($LASTEXITCODE -ne 0) { Write-Error "Failed to start SQL MCP sidecar container." docker network rm $NetworkName 2>&1 | Out-Null return } # Wait for the sidecar to become healthy. $timeout = 30 $elapsed = 0 $healthy = $false while ($elapsed -lt $timeout) { $status = docker inspect --format '{{.State.Health.Status}}' $sidecarName 2>$null if ($status -eq 'healthy') { $healthy = $true break } if ($status -eq 'unhealthy') { break } Start-Sleep -Seconds 1 $elapsed++ } if (-not $healthy) { Write-Error "SQL MCP sidecar failed to become healthy within ${timeout}s." docker stop $sidecarName 2>&1 | Out-Null docker network rm $NetworkName 2>&1 | Out-Null return } Write-Host '[dclaude] SQL MCP sidecar ready.' -ForegroundColor DarkGray return [PSCustomObject]@{ NetworkName = $NetworkName SidecarName = $sidecarName McpUrl = 'http://sql-mcp:3100/mcp' } } |