Skip to content

Commit a09caf1

Browse files
committed
Added new cmdlets
1 parent 4d6cfb0 commit a09caf1

File tree

3 files changed

+650
-0
lines changed

3 files changed

+650
-0
lines changed
Lines changed: 300 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,300 @@
1+
function Grant-MsIdMcpServerPermission {
2+
<#
3+
.SYNOPSIS
4+
Grants delegated permissions to MCP clients for the Microsoft MCP Server for Enterprise.
5+
6+
.DESCRIPTION
7+
This cmdlet grants OAuth2 delegated permissions to MCP clients (like VS Code or Visual Studio)
8+
to access the Microsoft MCP Server for Enterprise. You can specify predefined clients or
9+
provide custom MCP client app IDs.
10+
11+
.PARAMETER MCPClient
12+
Specifies the Visual Studio client(s) to grant permissions to. Can be one or more of:
13+
'VisualStudioCode', 'VisualStudio', 'VisualStudioMSAL'.
14+
Either this parameter or MCPClientServicePrincipalId must be specified.
15+
16+
.PARAMETER MCPClientServicePrincipalId
17+
The service principal ID(s) of custom MCP client(s) to grant permissions to.
18+
Must be valid GUID format(s).
19+
Either this parameter or MCPClient must be specified.
20+
21+
.PARAMETER Scopes
22+
Specific scopes to grant. If not specified, all available scopes are granted.
23+
24+
.EXAMPLE
25+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
26+
Grant-MsIdMcpServerPermission
27+
Grants all available permissions to Visual Studio Code (default MCP client if none specified).
28+
29+
.EXAMPLE
30+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
31+
Grant-MsIdMcpServerPermission -MCPClient 'VisualStudioCode'
32+
Grants all available permissions to Visual Studio Code.
33+
34+
.EXAMPLE
35+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
36+
Grant-MsIdMcpServerPermission -MCPClient 'VisualStudio', 'VisualStudioCode'
37+
Grants all available permissions to Visual Studio and Visual Studio Code.
38+
39+
.EXAMPLE
40+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
41+
Grant-MsIdMcpServerPermission -MCPClientServicePrincipalId '12345678-1234-1234-1234-123456789012'
42+
Grants all available permissions to a custom MCP client using its service principal ID.
43+
44+
.EXAMPLE
45+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
46+
Grant-MsIdMcpServerPermission -MCPClient 'VisualStudioCode' -Scopes 'MCP.User.Read.All', 'MCP.Group.Read.All'
47+
Grant specific permissions to Visual Studio Code.
48+
49+
.EXAMPLE
50+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
51+
Grant-MsIdMcpServerPermission -MCPClient 'VisualStudioMSAL' -Scopes 'MCP.User.Read.All'
52+
Grants specific permissions to Visual Studio MSAL client.
53+
54+
.EXAMPLE
55+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
56+
Grant-MsIdMcpServerPermission -MCPClientServicePrincipalId '12345678-1234-1234-1234-123456789012' -Scopes 'MCP.User.Read.All'
57+
Grants specific permissions to a custom MCP client.
58+
#>
59+
[CmdletBinding(DefaultParameterSetName = 'PredefinedClients')]
60+
param(
61+
[Parameter(ParameterSetName = 'PredefinedClients', Mandatory = $false)]
62+
[Parameter(ParameterSetName = 'PredefinedClientsScopes', Mandatory = $true)]
63+
[ValidateSet('VisualStudioCode', 'VisualStudio', 'VisualStudioMSAL', 'ChatGpt', 'ClaudeDesktop')]
64+
[string[]]$MCPClient = @('VisualStudioCode'),
65+
66+
[Parameter(ParameterSetName = 'CustomClients', Mandatory = $true)]
67+
[Parameter(ParameterSetName = 'CustomClientsScopes', Mandatory = $true)]
68+
[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}$')]
69+
[string[]]$MCPClientServicePrincipalId,
70+
71+
[Parameter(ParameterSetName = 'PredefinedClients', Mandatory = $false)]
72+
[Parameter(ParameterSetName = 'PredefinedClientsScopes', Mandatory = $true)]
73+
[Parameter(ParameterSetName = 'CustomClientsScopes', Mandatory = $true)]
74+
[string[]]$Scopes
75+
)
76+
77+
begin {
78+
79+
## Initialize Critical Dependencies
80+
$CriticalError = $null
81+
if (!(Test-MgCommandPrerequisites 'Get-MgServicePrincipal', 'Get-MgOauth2PermissionGrant', 'New-MgOauth2PermissionGrant', 'Update-MgOauth2PermissionGrant', 'Remove-MgOauth2PermissionGrant' -MinimumVersion 2.8.0 -ErrorVariable CriticalError)) { return }
82+
83+
# Make sure required scopes are present
84+
if ($null -eq (Get-MgContext) -or -not (Get-MgContext).Scopes.Contains('DelegatedPermissionGrant.ReadWrite.All') -or -not (Get-MgContext).Scopes.Contains('Application.ReadWrite.All')) {
85+
Connect-MgGraph -Scopes DelegatedPermissionGrant.ReadWrite.All, Application.ReadWrite.All
86+
}
87+
88+
function Get-ServicePrincipal([string]$appId, [string]$name) {
89+
$sp = Get-MgServicePrincipal -Filter "appId eq '$appId'" -ErrorAction SilentlyContinue | Select-Object -First 1
90+
if (-not $sp) {
91+
Write-Verbose "Creating service principal for $name ..."
92+
$sp = New-MgServicePrincipal -AppId $appId
93+
}
94+
return $sp
95+
}
96+
97+
function Get-Grant {
98+
param(
99+
[Parameter(Mandatory)] [string] $ClientSpId,
100+
[Parameter(Mandatory)] [string] $ResourceSpId
101+
)
102+
Get-MgOauth2PermissionGrant `
103+
-Filter "clientId eq '$ClientSpId' and resourceId eq '$ResourceSpId' and consentType eq 'AllPrincipals'" `
104+
-Top 1 `
105+
-Property "id,scope,clientId,resourceId,consentType" `
106+
-ErrorAction SilentlyContinue |
107+
Select-Object -First 1
108+
}
109+
110+
function Set-ExactScopes([string]$clientSpId, [string]$resourceSpId, [string[]]$targetScopes) {
111+
$targetString = ($targetScopes | Sort-Object -Unique) -join ' '
112+
$grant = Get-Grant -clientSpId $clientSpId -resourceSpId $resourceSpId | Select-Object -First 1
113+
114+
if (-not $targetScopes -or $targetScopes.Count -eq 0) {
115+
if ($grant) {
116+
Write-Verbose "Removing existing grant..."
117+
Remove-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id -Confirm:$false
118+
}
119+
return $null
120+
}
121+
122+
if (-not $grant) {
123+
Write-Verbose "Creating new permission grant..."
124+
$body = @{
125+
clientId = $clientSpId
126+
resourceId = $resourceSpId
127+
consentType = "AllPrincipals"
128+
scope = $targetString
129+
}
130+
return (@(New-MgOauth2PermissionGrant -BodyParameter $body)[0])
131+
}
132+
133+
$currentScope = if ($grant.Scope) { $grant.Scope } else { "" }
134+
if ($currentScope -ceq $targetString) {
135+
Write-Verbose "Grant already has the correct scopes."
136+
return $grant
137+
}
138+
139+
Write-Verbose "Updating existing permission grant..."
140+
Update-MgOauth2PermissionGrant -OAuth2PermissionGrantId $grant.Id -BodyParameter @{ scope = $targetString }
141+
return Get-Grant -clientSpId $clientSpId -resourceSpId $resourceSpId
142+
}
143+
144+
# Constants
145+
$resourceAppId = "e8c77dc2-69b3-43f4-bc51-3213c9d915b4" # Microsoft MCP Server for Enterprise
146+
$predefinedClients = @{
147+
"VisualStudioCode" = @{ Name = "Visual Studio Code"; AppId = "aebc6443-996d-45c2-90f0-388ff96faa56" }
148+
"VisualStudio" = @{ Name = "Visual Studio"; AppId = "04f0c124-f2bc-4f59-8241-bf6df9866bbd" }
149+
"VisualStudioMSAL" = @{ Name = "Visual Studio MSAL"; AppId = "62e61498-0c88-438b-a45c-2da0517bebe6" }
150+
"ChatGpt" = @{ Name = "ChatGPT"; AppId = "e0476654-c1d5-430b-ab80-70cbd947616a" }
151+
"ClaudeDesktop" = @{ Name = "Claude Desktop"; AppId = "08ad6f98-a4f8-4635-bb8d-f1a3044760f0" }
152+
}
153+
154+
function Resolve-MCPClient {
155+
param(
156+
[string[]]$MCPClients,
157+
[string[]]$CustomServicePrincipalIds
158+
)
159+
160+
$resolvedClients = @()
161+
162+
# Process MCP clients
163+
if ($MCPClients) {
164+
foreach ($client in $MCPClients) {
165+
if ($predefinedClients.ContainsKey($client)) {
166+
$clientInfo = $predefinedClients[$client]
167+
$resolvedClients += @{
168+
Name = $clientInfo.Name
169+
AppId = $clientInfo.AppId
170+
IsCustom = $false
171+
}
172+
}
173+
}
174+
}
175+
176+
# Process custom service principal IDs
177+
if ($CustomServicePrincipalIds) {
178+
foreach ($spId in $CustomServicePrincipalIds) {
179+
$resolvedClients += @{
180+
Name = "Custom MCP Client"
181+
AppId = $spId
182+
IsCustom = $true
183+
}
184+
}
185+
}
186+
187+
return $resolvedClients
188+
}
189+
}
190+
191+
process {
192+
if ($CriticalError) { return }
193+
194+
# Get resource service principal
195+
$resourceSp = Get-ServicePrincipal $resourceAppId "Microsoft MCP Server for Enterprise"
196+
197+
# Get available delegated scopes
198+
$availableScopes = $resourceSp.Oauth2PermissionScopes | Where-Object IsEnabled | Select-Object -ExpandProperty Value
199+
if (-not $availableScopes) {
200+
throw "Resource app exposes no enabled delegated (user) scopes."
201+
}
202+
$availableScopes = $availableScopes | Sort-Object -Unique
203+
204+
# Resolve MCP clients
205+
$clients = Resolve-MCPClient -MCPClients $MCPClient -CustomServicePrincipalIds $MCPClientServicePrincipalId
206+
Write-Verbose "Resolved $($clients.Count) MCP client(s): $($clients.Name -join ', ')"
207+
208+
# Get service principals for the resolved clients
209+
$clientSps = @()
210+
foreach ($client in $clients) {
211+
try {
212+
$sp = Get-ServicePrincipal $client.AppId $client.Name
213+
$clientSps += @{
214+
Sp = $sp
215+
Name = $client.Name
216+
IsCustom = $client.IsCustom
217+
}
218+
Write-Verbose "Found service principal for: $($client.Name)"
219+
}
220+
catch {
221+
Write-Warning "Could not get service principal for $($client.Name) (App ID: $($client.AppId)): $($_.Exception.Message)"
222+
}
223+
}
224+
225+
if ($clientSps.Count -eq 0) {
226+
throw "No MCP client service principals could be found or created."
227+
}
228+
229+
Write-Host "Operating on $($clientSps.Count) MCP client(s): $($clientSps.Name -join ', ')" -ForegroundColor Cyan
230+
231+
# Determine target scopes
232+
if ($PSCmdlet.ParameterSetName -like '*Scopes') {
233+
# Validate specified scopes
234+
$invalidScopes = $Scopes | Where-Object { $_ -notin $availableScopes }
235+
if ($invalidScopes) {
236+
throw "Invalid scopes (not available on resource): $($invalidScopes -join ', ')"
237+
}
238+
$targetScopes = $Scopes | Sort-Object -Unique
239+
Write-Host "Granting specific scopes: $($targetScopes -join ', ')" -ForegroundColor Cyan
240+
}
241+
else {
242+
# Grant all available scopes (default behavior)
243+
$targetScopes = $availableScopes
244+
Write-Host "Granting all available scopes: $($targetScopes -join ', ')" -ForegroundColor Cyan
245+
}
246+
247+
# Apply the permission grants to all client service principals
248+
$results = @()
249+
foreach ($clientSp in $clientSps) {
250+
try {
251+
$grant = Set-ExactScopes -clientSpId $clientSp.Sp.Id -resourceSpId $resourceSp.Id -targetScopes $targetScopes
252+
$results += @{
253+
Client = $clientSp.Name
254+
Grant = $grant
255+
Success = $true
256+
Error = $null
257+
}
258+
}
259+
catch {
260+
$results += @{
261+
Client = $clientSp.Name
262+
Grant = $null
263+
Success = $false
264+
Error = $_.Exception.Message
265+
}
266+
}
267+
}
268+
269+
# Display results
270+
$successCount = ($results | Where-Object Success | Measure-Object).Count
271+
$errorCount = ($results | Where-Object { -not $_.Success } | Measure-Object).Count
272+
273+
Write-Host "`nResults Summary:" -ForegroundColor Yellow
274+
Write-Host "Successfully processed: $successCount client(s)" -ForegroundColor Green
275+
if ($errorCount -gt 0) {
276+
Write-Host "Failed to process: $errorCount client(s)" -ForegroundColor Red
277+
}
278+
279+
foreach ($result in $results) {
280+
if ($result.Success) {
281+
if ($result.Grant) {
282+
Write-Host "`n✓ Successfully granted permissions to $($result.Client)" -ForegroundColor Green
283+
Write-Host " Grant ID: $($result.Grant.Id)" -ForegroundColor Gray
284+
285+
# Display granted scopes
286+
$grantedScopes = ($result.Grant.Scope -split '\s+' | Where-Object { $_ }) | Sort-Object
287+
Write-Host " Granted scopes:" -ForegroundColor Yellow
288+
$grantedScopes | ForEach-Object { Write-Host " - $_" -ForegroundColor Green }
289+
}
290+
else {
291+
Write-Host "`n⚠ No permissions were granted to $($result.Client) (empty scope list)" -ForegroundColor Yellow
292+
}
293+
}
294+
else {
295+
Write-Host "`n✗ Failed to grant permissions to $($result.Client)" -ForegroundColor Red
296+
Write-Host " Error: $($result.Error)" -ForegroundColor Red
297+
}
298+
}
299+
}
300+
}

src/MSIdentityTools.psd1

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@
126126
'.\Get-MsIdSamlFederationMetadata.ps1'
127127
'.\Get-MsIdServicePrincipalIdByAppId.ps1'
128128
'.\Get-MsIdUnmanagedExternalUser.ps1'
129+
'.\Grant-MsIdMcpServerPermission.ps1'
129130
'.\Invoke-MsIdAzureAdSamlRequest.ps1'
130131
'.\New-MsIdClientSecret.ps1'
131132
'.\New-MsIdSamlRequest.ps1'
@@ -134,6 +135,7 @@
134135
'.\Remove-MsIdUserAuthenticationMethod.ps1'
135136
'.\Reset-MsIdExternalUser.ps1'
136137
'.\Resolve-MsIdAzureIpAddress.ps1'
138+
'.\Revoke-MsIdMcpServerPermission.ps1'
137139
'.\Revoke-MsIdServicePrincipalConsent.ps1'
138140
'.\Set-MsIdCbaAuthMethodPolicy.ps1'
139141
'.\Set-MsIdCbaCertificateAuthority.ps1'
@@ -189,6 +191,7 @@
189191
'Get-MsIdSamlFederationMetadata'
190192
'Get-MsIdServicePrincipalIdByAppId'
191193
'Get-MsIdUnmanagedExternalUser'
194+
'Grant-MsIdMcpServerPermission'
192195
'Invoke-MsIdAzureAdSamlRequest'
193196
'New-MsIdWsTrustRequest'
194197
'New-MsIdClientSecret'
@@ -197,6 +200,7 @@
197200
'Remove-MsIdUserAuthenticationMethod'
198201
'Reset-MsIdExternalUser'
199202
'Resolve-MsIdTenant'
203+
'Revoke-MsIdMcpServerPermission'
200204
'Revoke-MsIdServicePrincipalConsent'
201205
'Set-MsIdCbaAuthMethodPolicy'
202206
'Set-MsIdCbaCertificateAuthority'

0 commit comments

Comments
 (0)