sync: SHANGHAI-U9 2026-07-27 修复 MCP 双向同步向 Claude 注册失败

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 并连通。
This commit is contained in:
bowei
2026-07-27 01:15:27 +08:00
parent 8080703a0e
commit 6dddab8923
+37 -13
View File
@@ -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) {