Grant-MsIdMcpServerPermission.ps1
|
function Grant-MsIdMcpServerPermission { <# .SYNOPSIS Grants delegated permissions to MCP clients for the Microsoft MCP Server for Enterprise. .DESCRIPTION This cmdlet grants OAuth2 delegated permissions to MCP clients (like VS Code or Visual Studio) to access the Microsoft MCP Server for Enterprise. You can specify predefined clients or provide custom MCP client app IDs. .PARAMETER MCPClient Specifies the Visual Studio client(s) to grant permissions to. Can be one or more of: 'VisualStudioCode', 'VisualStudio', 'VisualStudioMSAL'. Either this parameter or MCPClientServicePrincipalId must be specified. .PARAMETER MCPClientServicePrincipalId The service principal ID(s) of custom MCP client(s) to grant permissions to. Must be valid GUID format(s). Either this parameter or MCPClient must be specified. .PARAMETER Scopes Specific scopes to grant. If not specified, all available scopes are granted. .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission Grants all available permissions to Visual Studio Code (default MCP client if none specified). .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission -MCPClient 'VisualStudioCode' Grants all available permissions to Visual Studio Code. .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission -MCPClient 'VisualStudio', 'VisualStudioCode' Grants all available permissions to Visual Studio and Visual Studio Code. .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission -MCPClientServicePrincipalId '12345678-1234-1234-1234-123456789012' Grants all available permissions to a custom MCP client using its service principal ID. .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission -MCPClient 'VisualStudioCode' -Scopes 'MCP.User.Read.All', 'MCP.Group.Read.All' Grant specific permissions to Visual Studio Code. .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission -MCPClient 'VisualStudioMSAL' -Scopes 'MCP.User.Read.All' Grants specific permissions to Visual Studio MSAL client. .EXAMPLE Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All Grant-MsIdMcpServerPermission -MCPClientServicePrincipalId '12345678-1234-1234-1234-123456789012' -Scopes 'MCP.User.Read.All' Grants specific permissions to a custom MCP client. #> [CmdletBinding(DefaultParameterSetName = 'PredefinedClients')] param( [Parameter(ParameterSetName = 'PredefinedClients', Mandatory = $false)] [Parameter(ParameterSetName = 'PredefinedClientsScopes', Mandatory = $true)] [ValidateSet('VisualStudioCode', 'VisualStudio', 'VisualStudioMSAL', 'ChatGpt', 'ClaudeDesktop')] [string[]]$MCPClient = @('VisualStudioCode'), [Parameter(ParameterSetName = 'CustomClients', Mandatory = $true)] [Parameter(ParameterSetName = 'CustomClientsScopes', Mandatory = $true)] [ValidatePattern('^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')] [string[]]$MCPClientServicePrincipalId, [Parameter(ParameterSetName = 'PredefinedClients', Mandatory = $false)] [Parameter(ParameterSetName = 'PredefinedClientsScopes', Mandatory = $true)] [Parameter(ParameterSetName = 'CustomClientsScopes', Mandatory = $true)] [string[]]$Scopes ) begin { ## Initialize Critical Dependencies $CriticalError = $null if (!(Test-MgCommandPrerequisites 'Get-MgServicePrincipal', 'Get-MgOauth2PermissionGrant', 'New-MgOauth2PermissionGrant', 'Update-MgOauth2PermissionGrant', 'Remove-MgOauth2PermissionGrant' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return } # Make sure required scopes are present if ($null -eq (Get-MgContext) -or -not (Get-MgContext).Scopes.Contains('DelegatedPermissionGrant.ReadWrite.All') -or -not (Get-MgContext).Scopes.Contains('Application.ReadWrite.All')) { Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All } function Get-ServicePrincipal([string]$appId, [string]$name) { $sp = Get-MgServicePrincipal -Filter "appId eq '$appId'" -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $sp) { Write-Verbose "Creating service principal for $name ..." $sp = New-MgServicePrincipal -AppId $appId } return $sp } function Get-Grant { param( [Parameter(Mandatory)] [string] $ClientSpId, [Parameter(Mandatory)] [string] $ResourceSpId ) Get-MgOauth2PermissionGrant ` -Filter "clientId eq '$ClientSpId' and resourceId eq '$ResourceSpId' and consentType eq 'AllPrincipals'" ` -Top 1 ` -Property "id,scope,clientId,resourceId,consentType" ` -ErrorAction SilentlyContinue | Select-Object -First 1 } function Set-ExactScopes([string]$clientSpId, [string]$resourceSpId, [string[]]$targetScopes) { $targetString = ($targetScopes | Sort-Object -Unique) -join ' ' $grant = Get-Grant -clientSpId $clientSpId -resourceSpId $resourceSpId | Select-Object -First 1 if (-not $targetScopes -or $targetScopes.Count -eq 0) { if ($grant) { Write-Verbose "Removing existing grant..." Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id -Confirm:$false } return $null } if (-not $grant) { Write-Verbose "Creating new permission grant..." $body = @{ clientId = $clientSpId resourceId = $resourceSpId consentType = "AllPrincipals" scope = $targetString } return (@(New-MgOauth2PermissionGrant -BodyParameter $body)[0]) } $currentScope = if ($grant.Scope) { $grant.Scope } else { "" } if ($currentScope -ceq $targetString) { Write-Verbose "Grant already has the correct scopes." return $grant } Write-Verbose "Updating existing permission grant..." Update-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id -BodyParameter @{ scope = $targetString } return Get-Grant -clientSpId $clientSpId -resourceSpId $resourceSpId } # Constants $resourceAppId = "e8c77dc2-69b3-43f4-bc51-3213c9d915b4" # Microsoft MCP Server for Enterprise $predefinedClients = @{ "VisualStudioCode" = @{ Name = "Visual Studio Code"; AppId = "aebc6443-996d-45c2-90f0-388ff96faa56" } "VisualStudio" = @{ Name = "Visual Studio"; AppId = "04f0c124-f2bc-4f59-8241-bf6df9866bbd" } "VisualStudioMSAL" = @{ Name = "Visual Studio MSAL"; AppId = "62e61498-0c88-438b-a45c-2da0517bebe6" } "ChatGpt" = @{ Name = "ChatGPT"; AppId = "e0476654-c1d5-430b-ab80-70cbd947616a" } "ClaudeDesktop" = @{ Name = "Claude Desktop"; AppId = "08ad6f98-a4f8-4635-bb8d-f1a3044760f0" } } function Resolve-MCPClient { param( [string[]]$MCPClients, [string[]]$CustomServicePrincipalIds ) $resolvedClients = @() # Process MCP clients if ($MCPClients) { foreach ($client in $MCPClients) { if ($predefinedClients.ContainsKey($client)) { $clientInfo = $predefinedClients[$client] $resolvedClients += @{ Name = $clientInfo.Name AppId = $clientInfo.AppId IsCustom = $false } } } } # Process custom service principal IDs if ($CustomServicePrincipalIds) { foreach ($spId in $CustomServicePrincipalIds) { $resolvedClients += @{ Name = "Custom MCP Client" AppId = $spId IsCustom = $true } } } return $resolvedClients } } process { if ($CriticalError) { return } # Get resource service principal $resourceSp = Get-ServicePrincipal $resourceAppId "Microsoft MCP Server for Enterprise" # Get available delegated scopes $availableScopes = $resourceSp.Oauth2PermissionScopes | Where-Object IsEnabled | Select-Object -ExpandProperty Value if (-not $availableScopes) { throw "Resource app exposes no enabled delegated (user) scopes." } $availableScopes = $availableScopes | Sort-Object -Unique # Resolve MCP clients $clients = Resolve-MCPClient -MCPClients $MCPClient -CustomServicePrincipalIds $MCPClientServicePrincipalId Write-Verbose "Resolved $($clients.Count) MCP client(s): $($clients.Name -join ', ')" # Get service principals for the resolved clients $clientSps = @() foreach ($client in $clients) { try { $sp = Get-ServicePrincipal $client.AppId $client.Name $clientSps += @{ Sp = $sp Name = $client.Name IsCustom = $client.IsCustom } Write-Verbose "Found service principal for: $($client.Name)" } catch { Write-Warning "Could not get service principal for $($client.Name) (App ID: $($client.AppId)): $($_.Exception.Message)" } } if ($clientSps.Count -eq 0) { throw "No MCP client service principals could be found or created." } Write-Host "Operating on $($clientSps.Count) MCP client(s): $($clientSps.Name -join ', ')" -ForegroundColor Cyan # Determine target scopes if ($PSCmdlet.ParameterSetName -like '*Scopes') { # Validate specified scopes $invalidScopes = $Scopes | Where-Object { $_ -notin $availableScopes } if ($invalidScopes) { throw "Invalid scopes (not available on resource): $($invalidScopes -join ', ')" } $targetScopes = $Scopes | Sort-Object -Unique Write-Host "Granting specific scopes: $($targetScopes -join ', ')" -ForegroundColor Cyan } else { # Grant all available scopes (default behavior) $targetScopes = $availableScopes Write-Host "Granting all available scopes: $($targetScopes -join ', ')" -ForegroundColor Cyan } # Apply the permission grants to all client service principals $results = @() foreach ($clientSp in $clientSps) { try { $grant = Set-ExactScopes -clientSpId $clientSp.Sp.Id -resourceSpId $resourceSp.Id -targetScopes $targetScopes $results += @{ Client = $clientSp.Name Grant = $grant Success = $true Error = $null } } catch { $results += @{ Client = $clientSp.Name Grant = $null Success = $false Error = $_.Exception.Message } } } # Display results $successCount = ($results | Where-Object Success | Measure-Object).Count $errorCount = ($results | Where-Object { -not $_.Success } | Measure-Object).Count Write-Host "`nResults Summary:" -ForegroundColor Yellow Write-Host "Successfully processed: $successCount client(s)" -ForegroundColor Green if ($errorCount -gt 0) { Write-Host "Failed to process: $errorCount client(s)" -ForegroundColor Red } foreach ($result in $results) { if ($result.Success) { if ($result.Grant) { Write-Host "`n✓ Successfully granted permissions to $($result.Client)" -ForegroundColor Green Write-Host " Grant ID: $($result.Grant.Id)" -ForegroundColor Gray # Display granted scopes $grantedScopes = ($result.Grant.Scope -split '\s+' | Where-Object { $_ }) | Sort-Object Write-Host " Granted scopes:" -ForegroundColor Yellow $grantedScopes | ForEach-Object { Write-Host " - $_" -ForegroundColor Green } } else { Write-Host "`n⚠ No permissions were granted to $($result.Client) (empty scope list)" -ForegroundColor Yellow } } else { Write-Host "`n✗ Failed to grant permissions to $($result.Client)" -ForegroundColor Red Write-Host " Error: $($result.Error)" -ForegroundColor Red } } } } |