# Builds a Cursor MCP install deeplink. # config = base64(JSON.stringify({command, args, ...})) -- transport only # name = server key (separate query param) # Wrapping as { serverName: transport } makes Cursor show command: "". # Usage: . scripts\cursor-deeplink.ps1; New-CursorDeeplink -ServerName wordcraft -Config @{ command='...'; args=@('mcp') } param( [string]$Name, [string]$ConfigJson ) . (Join-Path $PSScriptRoot 'lib.ps1') function New-CursorDeeplink([string]$ServerName, $Config) { $command = $null $argList = @() $extra = @{} if ($Config -is [string]) { $Config = $Config | ConvertFrom-Json } if ($Config -is [hashtable]) { if ($Config.ContainsKey($ServerName) -and $Config[$ServerName] -is [hashtable]) { $Config = $Config[$ServerName] } $command = [string]$Config['command'] if ($Config.ContainsKey('args')) { $argList = @($Config['args']) } foreach ($k in $Config.Keys) { if ($k -ne 'command' -and $k -ne 'args') { $extra[$k] = $Config[$k] } } } else { # PSCustomObject $props = @($Config.PSObject.Properties.Name) if ($props.Count -eq 1 -and $props[0] -eq $ServerName) { $Config = $Config.$ServerName $props = @($Config.PSObject.Properties.Name) } $command = [string]$Config.command if ($null -ne $Config.args) { $argList = @($Config.args) } foreach ($p in $Config.PSObject.Properties) { if ($p.Name -ne 'command' -and $p.Name -ne 'args') { $extra[$p.Name] = $p.Value } } } # Forward slashes: valid on Windows, safer through JSON/URL pipelines. if ($command -match '^[A-Za-z]:\\') { $command = $command -replace '\\', '/' } $transport = [ordered]@{ command = $command; args = @($argList) } foreach ($k in $extra.Keys) { $transport[$k] = $extra[$k] } $jsonPath = [IO.Path]::GetTempFileName() try { # Write via .NET so Python can re-dump with JSON.stringify-compatible compact form. $psJson = ($transport | ConvertTo-Json -Compress -Depth 20) [IO.File]::WriteAllText($jsonPath, $psJson, $Utf8NoBom) $json = & python -c "import json,sys; print(json.dumps(json.load(open(sys.argv[1],encoding='utf-8')),separators=(',',':'),ensure_ascii=False))" $jsonPath if (-not $json) { $json = $psJson } } finally { Remove-Item -LiteralPath $jsonPath -Force -ErrorAction SilentlyContinue } $bytes = $Utf8NoBom.GetBytes($json) $b64 = [Convert]::ToBase64String($bytes).TrimEnd('=').Replace('+', '-').Replace('/', '_') return "cursor://anysphere.cursor-deeplink/mcp/install?name=$([Uri]::EscapeDataString($ServerName))&config=$b64" } if ($Name -and $ConfigJson) { Write-Output (New-CursorDeeplink -ServerName $Name -Config $ConfigJson) }