Files
ITWorx_MCP_Hub-Public/scripts/install_codex_mcp.ps1
T

98 lines
3.8 KiB
PowerShell

[CmdletBinding()]
param(
[Parameter(Mandatory = $true)]
[ValidatePattern('^[a-z0-9][a-z0-9-]*$')]
[string]$Project,
[Parameter(Mandatory = $true)]
[ValidatePattern('^[a-z0-9][a-z0-9-]*$')]
[string]$Environment,
[Parameter(Mandatory = $true)]
[ValidatePattern('^[0-9a-fA-F-]{36}$')]
[string]$HubClientId,
[Parameter(Mandatory = $true)]
[ValidatePattern('^[a-zA-Z0-9_.@-]+$')]
[string]$OAuthUsername,
[Parameter(Mandatory = $true)]
[string]$SecretFile,
[string]$PublicBaseUrl = 'https://mcp.example.com',
[string]$TokenEndpoint = 'https://auth.example-host.com/application/o/token/',
[string]$OAuthClientId = 'itworx-mcp-hub',
[string]$PythonExecutable = '',
[string]$CodexExecutable = '',
[switch]$Force
)
$ErrorActionPreference = 'Stop'
$repositoryRoot = Split-Path -Parent $PSScriptRoot
$bridge = Join-Path $PSScriptRoot 'mcp_stdio_bridge.py'
$resolvedSecret = (Resolve-Path -LiteralPath $SecretFile).Path
if (-not (Test-Path -LiteralPath $bridge -PathType Leaf)) {
throw 'MCP bridge script is missing.'
}
$secretInfo = Get-Item -LiteralPath $resolvedSecret
if (-not $secretInfo.PSIsContainer -and $secretInfo.Length -gt 0 -and $secretInfo.Length -le 8192) {
# Continue; content is deliberately never read or printed by this installer.
} else {
throw 'Secret file must be a non-empty regular file no larger than 8192 bytes.'
}
if (-not $PublicBaseUrl.StartsWith('https://') -or -not $TokenEndpoint.StartsWith('https://')) {
throw 'Public MCP and OAuth endpoints must use HTTPS.'
}
if (-not $PythonExecutable) {
$PythonExecutable = (Get-Command python -ErrorAction Stop).Source
}
if (-not $CodexExecutable) {
$CodexExecutable = (Get-Command codex -ErrorAction Stop).Source
}
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent().Name
$currentAcl = Get-Acl -LiteralPath $resolvedSecret
if ($currentAcl.Owner -ne $identity) {
throw 'Secret file must already be owned by the current Windows identity.'
}
$verifiedAcl = $currentAcl
$verifiedRules = @($verifiedAcl.Access)
if (
-not $verifiedAcl.AreAccessRulesProtected -or
$verifiedRules.Count -ne 1 -or
$verifiedRules[0].IdentityReference.Value -ne $identity -or
$verifiedRules[0].AccessControlType -ne [System.Security.AccessControl.AccessControlType]::Allow -or
($verifiedRules[0].FileSystemRights -band [System.Security.AccessControl.FileSystemRights]::FullControl) -ne [System.Security.AccessControl.FileSystemRights]::FullControl
) {
throw 'Secret file ACL must grant only the current Windows identity FullControl; use new_codex_credential.ps1.'
}
$name = "itworx-$Project-$Environment"
$endpoint = "$($PublicBaseUrl.TrimEnd('/'))/mcp/$Project/$Environment"
$existing = & $CodexExecutable mcp get $name 2>$null
if ($LASTEXITCODE -eq 0) {
if (-not $Force) {
throw "Codex MCP server '$name' already exists. Re-run with -Force to replace it."
}
& $CodexExecutable mcp remove $name | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Could not remove existing Codex MCP server '$name'." }
}
$arguments = @(
'mcp', 'add', $name, '--',
$PythonExecutable, $bridge,
'--endpoint', $endpoint,
'--client-id', $HubClientId,
'--token-endpoint', $TokenEndpoint,
'--oauth-client-id', $OAuthClientId,
'--oauth-username', $OAuthUsername,
'--client-secret-file', $resolvedSecret,
'--scope', 'openid profile'
)
& $CodexExecutable @arguments | Out-Null
if ($LASTEXITCODE -ne 0) { throw "Could not add Codex MCP server '$name'." }
& $CodexExecutable mcp get $name
if ($LASTEXITCODE -ne 0) { throw "Codex MCP server '$name' was not readable after installation." }
Write-Output "Configured $name for $endpoint. Restart Codex before first use."