From 6dddab89230343fb0d2b514868d40f4388c2a4e5 Mon Sep 17 00:00:00 2001 From: bowei Date: Mon, 27 Jul 2026 01:15:27 +0800 Subject: [PATCH] =?UTF-8?q?sync:=20SHANGHAI-U9=202026-07-27=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20MCP=20=E5=8F=8C=E5=90=91=E5=90=8C=E6=AD=A5=E5=90=91?= =?UTF-8?q?=20Claude=20=E6=B3=A8=E5=86=8C=E5=A4=B1=E8=B4=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add-ToClaude 改用 `claude mcp add ... --` 标志形式,不再走 add-json: Windows PowerShell 5.1 会吞掉传给原生 exe 的 JSON 参数里的双引号, claude.exe 收到残缺 JSON 后报 "Invalid configuration: : Invalid input"。 另外 add-json 即使在引号完好时也拒绝反斜杠路径,故新增 ConvertTo-PortablePath,把带盘符的路径统一成正斜杠;同一函数也用于 比较,避免 C:\x 与 C:/x 被误判成 drift。 实测:drawio(Codex 独有)现已能由 -Apply 自动补齐到 Claude 并连通。 --- tools-sync/scripts/sync_mcp_registrations.ps1 | 50 ++++++++++++++----- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/tools-sync/scripts/sync_mcp_registrations.ps1 b/tools-sync/scripts/sync_mcp_registrations.ps1 index b87d912..4ae2055 100644 --- a/tools-sync/scripts/sync_mcp_registrations.ps1 +++ b/tools-sync/scripts/sync_mcp_registrations.ps1 @@ -116,6 +116,16 @@ function Get-PortableMcps($Map, [string]$Side) { return $result } +function ConvertTo-PortablePath([string]$Value) { + # `claude mcp add-json` rejects Windows backslash paths ("Invalid configuration: + # : Invalid input"); forward slashes work on Windows and are what the CLI itself + # writes. Only rewrite strings that actually carry a drive-letter path, so flags + # and non-path args are left alone (`--extension-file=C:\..` is rewritten too). + if ($null -eq $Value) { return $Value } + if ($Value -match '[A-Za-z]:\\') { return ($Value -replace '\\', '/') } + return $Value +} + function Get-ComparableJson($Mcp) { $envMap = [ordered]@{} foreach ($key in @($Mcp.env.Keys) | Sort-Object) { @@ -126,10 +136,13 @@ function Get-ComparableJson($Mcp) { foreach ($key in @($Mcp.headers.Keys) | Sort-Object) { $headers[$key] = [string]$Mcp.headers[$key] } + # `C:\x\y` and `C:/x/y` are the same target on Windows -- the two CLIs write + # different separators, so compare on a single normalized form or every path + # arg reads as drift. $value = [ordered]@{ transport = $Mcp.transport - command = $Mcp.command - args = @($Mcp.args) + command = (ConvertTo-PortablePath $Mcp.command) + args = @(@($Mcp.args) | ForEach-Object { ConvertTo-PortablePath $_ }) env = $envMap url = $Mcp.url headers = $headers @@ -175,21 +188,32 @@ function Add-ToCodex($Mcp) { } function Add-ToClaude($Mcp) { + # Deliberately NOT `claude mcp add-json`: Windows PowerShell 5.1 strips the + # double quotes out of a JSON argument on its way to a native exe, so claude.exe + # sees malformed JSON and dies with "Invalid configuration: : Invalid input". + # The flag form takes plain tokens and is immune. (Separately, add-json rejects + # backslash paths even when quoting is clean -- hence ConvertTo-PortablePath.) if ($Mcp.transport -eq "stdio") { - $envMap = [ordered]@{} + $arguments = @("mcp", "add", $Mcp.name, "--scope", "user") foreach ($key in $Mcp.env.Keys) { - if ($CodexOnlyEnv -notcontains $key) { $envMap[$key] = $Mcp.env[$key] } + if ($CodexOnlyEnv -contains $key) { continue } + $arguments += @("--env", "$key=$($Mcp.env[$key])") } - $cfg = [ordered]@{ type = "stdio"; command = $Mcp.command; args = @($Mcp.args); env = $envMap } - } else { - if ($Mcp.bearer_token_env_var) { - throw "Codex bearer_token_env_var has no lossless Claude JSON translation: $($Mcp.name)" - } - $cfg = [ordered]@{ type = $Mcp.transport; url = $Mcp.url } - if ($Mcp.headers.Count -gt 0) { $cfg.headers = $Mcp.headers } + $arguments += "--" + $arguments += (ConvertTo-PortablePath $Mcp.command) + foreach ($arg in @($Mcp.args)) { $arguments += (ConvertTo-PortablePath $arg) } + Invoke-External "claude add $($Mcp.name)" "claude" $arguments + return } - $json = $cfg | ConvertTo-Json -Depth 8 -Compress - Invoke-External "claude add $($Mcp.name)" "claude" @("mcp", "add-json", "--scope", "user", $Mcp.name, $json) + if ($Mcp.bearer_token_env_var) { + throw "Codex bearer_token_env_var has no lossless Claude translation: $($Mcp.name)" + } + $arguments = @("mcp", "add", $Mcp.name, "--scope", "user", + "--transport", $Mcp.transport, $Mcp.url) + foreach ($key in $Mcp.headers.Keys) { + $arguments += @("--header", "$key`: $($Mcp.headers[$key])") + } + Invoke-External "claude add $($Mcp.name)" "claude" $arguments } function Remove-FromClaude([string]$Name) {