From d565a1bfb20e1fcee8b3062b8d58f151c07bf47e Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Mon, 15 Jun 2026 10:45:30 -0700 Subject: [PATCH 1/8] Handle Azure OpenAI v1 responses endpoint Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Remove Azure OpenAI endpoint tests Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Revert unnecessary change in the read me Add script for token generation and update readme to state this information Update to achieve parity with private sample agent Add Microsoft copyright/MIT header Signed-off-by: Denzel Pfeifer --- .../W365ComputerUseSample.sln | 14 +- .../ComputerUse/AzureOpenAIModelProvider.cs | 21 +- .../AzureOpenAIModelProviderOptions.cs | 31 +++ .../w365-computer-use/sample-agent/README.md | 47 ++++- .../sample-agent/appsettings.json | 3 +- .../scripts/Get-CuaAgentUserToken.ps1 | 189 ++++++++++++++++++ 6 files changed, 282 insertions(+), 23 deletions(-) create mode 100644 dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProviderOptions.cs create mode 100644 dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 diff --git a/dotnet/w365-computer-use/W365ComputerUseSample.sln b/dotnet/w365-computer-use/W365ComputerUseSample.sln index 67fe015d..f394dd85 100644 --- a/dotnet/w365-computer-use/W365ComputerUseSample.sln +++ b/dotnet/w365-computer-use/W365ComputerUseSample.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36623.8 @@ -8,13 +8,25 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x64.ActiveCfg = Debug|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x64.Build.0 = Debug|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x86.ActiveCfg = Debug|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x86.Build.0 = Debug|Any CPU {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|Any CPU.ActiveCfg = Release|Any CPU {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|Any CPU.Build.0 = Release|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x64.ActiveCfg = Release|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x64.Build.0 = Release|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x86.ActiveCfg = Release|Any CPU + {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProvider.cs b/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProvider.cs index 96a2a7c5..5847a27f 100644 --- a/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProvider.cs +++ b/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProvider.cs @@ -22,27 +22,12 @@ public AzureOpenAIModelProvider(IHttpClientFactory httpClientFactory, IConfigura { _httpClient = httpClientFactory.CreateClient("WebClient"); _logger = logger; - var endpoint = configuration["AIServices:AzureOpenAI:Endpoint"] - ?? throw new InvalidOperationException("AIServices:AzureOpenAI:Endpoint is required."); _apiKey = configuration["AIServices:AzureOpenAI:ApiKey"] ?? throw new InvalidOperationException("AIServices:AzureOpenAI:ApiKey is required."); - var apiVersion = configuration["AIServices:AzureOpenAI:ApiVersion"] ?? "2025-04-01-preview"; - // DeploymentName = deployment-based URL; ModelName = model-based URL (model sent in body) - var deploymentName = configuration["AIServices:AzureOpenAI:DeploymentName"]; - ModelName = configuration["AIServices:AzureOpenAI:ModelName"] - ?? deploymentName - ?? "computer-use-preview"; - - if (!string.IsNullOrEmpty(deploymentName)) - { - _url = $"{endpoint.TrimEnd('/')}/openai/deployments/{deploymentName}/responses?api-version={apiVersion}"; - } - else - { - // Model-based endpoint — model name goes in the request body, not the URL - _url = $"{endpoint.TrimEnd('/')}/openai/responses?api-version={apiVersion}"; - } + var options = AzureOpenAIModelProviderOptions.FromConfiguration(configuration); + ModelName = options.ModelName; + _url = options.Url; } public async Task SendAsync(string requestBody, CancellationToken cancellationToken) diff --git a/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProviderOptions.cs b/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProviderOptions.cs new file mode 100644 index 00000000..04e9ed1f --- /dev/null +++ b/dotnet/w365-computer-use/sample-agent/ComputerUse/AzureOpenAIModelProviderOptions.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +namespace W365ComputerUseSample.ComputerUse; + +internal sealed class AzureOpenAIModelProviderOptions +{ + public required string Url { get; init; } + + public required string ModelName { get; init; } + + public static AzureOpenAIModelProviderOptions FromConfiguration(IConfiguration configuration) + { + var endpoint = configuration["AIServices:AzureOpenAI:Endpoint"] + ?? throw new InvalidOperationException("AIServices:AzureOpenAI:Endpoint is required."); + + var configuredModelName = configuration["AIServices:AzureOpenAI:ModelName"]; + var deploymentName = configuration["AIServices:AzureOpenAI:DeploymentName"]; + var modelName = !string.IsNullOrWhiteSpace(configuredModelName) + ? configuredModelName + : !string.IsNullOrWhiteSpace(deploymentName) + ? deploymentName + : "computer-use-preview"; + + return new AzureOpenAIModelProviderOptions + { + ModelName = modelName, + Url = $"{endpoint.TrimEnd('/')}/openai/v1/responses", + }; + } +} diff --git a/dotnet/w365-computer-use/sample-agent/README.md b/dotnet/w365-computer-use/sample-agent/README.md index 57f9fad0..e8c7d9b8 100644 --- a/dotnet/w365-computer-use/sample-agent/README.md +++ b/dotnet/w365-computer-use/sample-agent/README.md @@ -82,6 +82,9 @@ Create `appsettings.Development.json` (this file is gitignored): } ``` +`DeploymentName` is treated as the model identifier fallback for compatibility with existing local settings. Azure OpenAI requests are sent to the v1 Responses endpoint (`/openai/v1/responses`), not the legacy deployment-style Responses URL. The selected `ModelName` or fallback `DeploymentName` is sent as the request body `model`. + + **For `gpt-5.4-mini` model:** ```json { @@ -101,7 +104,48 @@ Create `appsettings.Development.json` (this file is gitignored): ### 4. Obtain a bearer token -Get a token with the `McpServers.W365ComputerUse.All` scope for your tenant. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. +> **Note:** Running locally requires an agent identity. Create an Agent Blueprint with an Agent Identity for local development, then use that identity's client ID and the Agent Blueprint client credentials in the commands below. + +#### Get the Windows 365 for Agents MCP token + +Use the helper script to get a CUA user token for the MCP server, then set it as `BEARER_TOKEN`: + +```powershell + $tenantId = "" + $blueprintClientId = "" + $blueprintClientSecret = "" + $agentClientId = "" + $agentUpn = "" + + .\scripts\Get-CuaAgentUserToken.ps1 ` + -TenantId $tenantId ` + -AgentBlueprintClientId $blueprintClientId ` + -AgentBlueprintClientSecret $blueprintClientSecret ` + -AgentClientId $agentClientId ` + -AgentUsername $agentUpn ` + -SetBearerToken ` + -InformationAction Continue + ``` + + `-SetBearerToken` assigns the generated token to `$env:BEARER_TOKEN` for the current PowerShell process and writes an informational message. To use a different token audience, pass `-Scope ""`; by default the script requests `da81128c-e5b5-4f9e-8d89-50d906f107c5/.default`. + +The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. The script writes only the access token to stdout, so it can be assigned directly to `$env:BEARER_TOKEN`. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. + +#### Optional: Get a Microsoft Graph token for OneDrive screenshots + +This token is optional and is only needed when you want the sample to upload screenshots to OneDrive. + +```powershell +Install-Module MSAL.PS -Scope CurrentUser + +$token = Get-MsalToken ` + -ClientId "" ` + -TenantId "organizations" ` + -Scopes "https://graph.microsoft.com/Files.ReadWrite" ` + -Interactive + +$env:GRAPH_TOKEN = $token.AccessToken +``` ### 5. Start the MCP Platform server @@ -112,7 +156,6 @@ Ensure the MCP Platform is running locally on port 52857, or update the `McpServ ```powershell cd sample-agent $env:ASPNETCORE_ENVIRONMENT = "Development" -$env:BEARER_TOKEN = "" $env:GRAPH_TOKEN = "" dotnet run ``` diff --git a/dotnet/w365-computer-use/sample-agent/appsettings.json b/dotnet/w365-computer-use/sample-agent/appsettings.json index 25f2f3d9..c3ac3a0a 100644 --- a/dotnet/w365-computer-use/sample-agent/appsettings.json +++ b/dotnet/w365-computer-use/sample-agent/appsettings.json @@ -71,8 +71,7 @@ "DeploymentName": "<>", "ModelName": "", "Endpoint": "<>", - "ApiKey": "<>", - "ApiVersion": "2025-04-01-preview" + "ApiKey": "<>" } }, diff --git a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 new file mode 100644 index 00000000..2a52a516 --- /dev/null +++ b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 @@ -0,0 +1,189 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +<# +.SYNOPSIS +Acquires a CUA user access token for an agent using the Entra user_fic flow. + +.DESCRIPTION +Requests an application token, exchanges it for an agent identity token, and then +requests a CUA user token from Microsoft Entra ID. By default, the script writes +only the final CUA access token to stdout. Use -SetBearerToken to also assign +the token to $env:BEARER_TOKEN in the current PowerShell process. Use -ShowOid +to decode the final token payload and write the oid claim to the information +stream. + +.EXAMPLE +.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "secret" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" + +Writes only the final CUA access token to stdout. + +.EXAMPLE +.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "secret" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -SetBearerToken -InformationAction Continue + +Writes the final CUA access token to stdout and assigns it to $env:BEARER_TOKEN +in the current PowerShell process. + +.EXAMPLE +.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "secret" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -ShowOid -InformationAction Continue + +Writes the final CUA access token to stdout and writes the token oid claim to the information stream. +#> +[CmdletBinding()] +param( + [Parameter(Mandatory = $true)] + [string]$TenantId, + [Parameter(Mandatory = $true)] + [string]$AgentBlueprintClientId, + [Parameter(Mandatory = $true)] + [string]$AgentBlueprintClientSecret, + [Parameter(Mandatory = $true)] + [string]$AgentClientId, + [Parameter(Mandatory = $true)] + [string]$AgentUsername, + [string]$AuthorityHost = "https://login.microsoftonline.com", + [string]$Scope = "da81128c-e5b5-4f9e-8d89-50d906f107c5/.default", + [switch]$SetBearerToken, + [switch]$ShowOid +) + +$ErrorActionPreference = "Stop" + +function Assert-RequiredParameter { + param( + [Parameter(Mandatory = $true)] + [string]$Name, + + [AllowNull()] + [string]$Value + ) + + if ([string]::IsNullOrWhiteSpace($Value)) { + throw "Parameter validation failed: -$Name is required." + } +} + +function Get-AccessTokenFromResponse { + param( + [Parameter(Mandatory = $true)] + [object]$Response, + + [Parameter(Mandatory = $true)] + [string]$StepLabel + ) + + if ($null -eq $Response -or [string]::IsNullOrWhiteSpace($Response.access_token)) { + throw "$StepLabel failed: token response missing required field 'access_token'." + } + + return $Response.access_token +} + +function ConvertFrom-Base64Url { + param( + [Parameter(Mandatory = $true)] + [string]$Value + ) + + $base64 = $Value.Replace("-", "+").Replace("_", "/") + $padding = (4 - ($base64.Length % 4)) % 4 + if ($padding -gt 0) { + $base64 = $base64 + ("=" * $padding) + } + + $bytes = [Convert]::FromBase64String($base64) + return [Text.Encoding]::UTF8.GetString($bytes) +} + +function Write-OidInformation { + param( + [Parameter(Mandatory = $true)] + [string]$AccessToken + ) + + try { + $segments = $AccessToken.Split(".") + if ($segments.Count -ne 3) { + Write-Warning "ShowOid decode failed: access token is not a valid JWT (expected 3 segments)." + return + } + + $payloadJson = ConvertFrom-Base64Url -Value $segments[1] + $claims = $payloadJson | ConvertFrom-Json + if ([string]::IsNullOrWhiteSpace($claims.oid)) { + Write-Warning "ShowOid decode completed but JWT payload did not contain an 'oid' claim." + return + } + + Write-Information $claims.oid + } + catch { + Write-Warning "ShowOid decode failed: $($_.Exception.Message)" + } +} + +Assert-RequiredParameter -Name "TenantId" -Value $TenantId +Assert-RequiredParameter -Name "AgentBlueprintClientId" -Value $AgentBlueprintClientId +Assert-RequiredParameter -Name "AgentBlueprintClientSecret" -Value $AgentBlueprintClientSecret +Assert-RequiredParameter -Name "AgentClientId" -Value $AgentClientId +Assert-RequiredParameter -Name "AgentUsername" -Value $AgentUsername +Assert-RequiredParameter -Name "AuthorityHost" -Value $AuthorityHost +Assert-RequiredParameter -Name "Scope" -Value $Scope + +$tokenUrl = "$($AuthorityHost.TrimEnd('/'))/$TenantId/oauth2/v2.0/token" +$clientAssertionType = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer" + +try { + $applicationTokenResponse = Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType "application/x-www-form-urlencoded" -Body @{ + client_id = $AgentBlueprintClientId + scope = "api://AzureADTokenExchange/.default" + grant_type = "client_credentials" + client_secret = $AgentBlueprintClientSecret + fmi_path = $AgentClientId + } + $applicationToken = Get-AccessTokenFromResponse -Response $applicationTokenResponse -StepLabel "Application token request" +} +catch { + throw "Application token request failed: $($_.Exception.Message)" +} + +try { + $agentIdentityTokenResponse = Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType "application/x-www-form-urlencoded" -Body @{ + client_id = $AgentClientId + scope = "api://AzureADTokenExchange/.default" + grant_type = "client_credentials" + client_assertion_type = $clientAssertionType + client_assertion = $applicationToken + } + $agentIdentityToken = Get-AccessTokenFromResponse -Response $agentIdentityTokenResponse -StepLabel "Agent identity token request" +} +catch { + throw "Agent identity token request failed: $($_.Exception.Message)" +} + +try { + $cuaTokenResponse = Invoke-RestMethod -Method Post -Uri $tokenUrl -ContentType "application/x-www-form-urlencoded" -Body @{ + client_id = $AgentClientId + scope = $Scope + grant_type = "user_fic" + client_assertion_type = $clientAssertionType + client_assertion = $applicationToken + username = $AgentUsername + user_federated_identity_credential = $agentIdentityToken + } + $cuaAccessToken = Get-AccessTokenFromResponse -Response $cuaTokenResponse -StepLabel "CUA token request" +} +catch { + throw "CUA token request failed: $($_.Exception.Message)" +} + +if ($ShowOid) { + Write-OidInformation -AccessToken $cuaAccessToken +} + +if ($SetBearerToken) { + $env:BEARER_TOKEN = $cuaAccessToken + Write-Information "Set `$env:BEARER_TOKEN for the current PowerShell process." +} + +Write-Output $cuaAccessToken \ No newline at end of file From 89179079de906d938b73b18c04b70f6a8b92bb67 Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Wed, 17 Jun 2026 16:38:05 -0700 Subject: [PATCH 2/8] Remove unnecessary change --- dotnet/w365-computer-use/W365ComputerUseSample.sln | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/dotnet/w365-computer-use/W365ComputerUseSample.sln b/dotnet/w365-computer-use/W365ComputerUseSample.sln index f394dd85..67fe015d 100644 --- a/dotnet/w365-computer-use/W365ComputerUseSample.sln +++ b/dotnet/w365-computer-use/W365ComputerUseSample.sln @@ -1,4 +1,4 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.14.36623.8 @@ -8,25 +8,13 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x64.ActiveCfg = Debug|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x64.Build.0 = Debug|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x86.ActiveCfg = Debug|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Debug|x86.Build.0 = Debug|Any CPU {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|Any CPU.ActiveCfg = Release|Any CPU {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|Any CPU.Build.0 = Release|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x64.ActiveCfg = Release|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x64.Build.0 = Release|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x86.ActiveCfg = Release|Any CPU - {B72D1A3E-4F8C-9E56-A1B2-C3D4E5F60789}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From 041b54ee0e4001174674c09aad9e3e3506fa63f6 Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Thu, 18 Jun 2026 09:37:44 -0700 Subject: [PATCH 3/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../sample-agent/scripts/Get-CuaAgentUserToken.ps1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 index 2a52a516..8b33202c 100644 --- a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 +++ b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 @@ -92,8 +92,7 @@ function ConvertFrom-Base64Url { } $bytes = [Convert]::FromBase64String($base64) - return [Text.Encoding]::UTF8.GetString($bytes) -} + return [System.Text.Encoding]::UTF8.GetString($bytes) function Write-OidInformation { param( From 831d47d07bb7fadf5b9a912e62fa1b34ccd500da Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Thu, 18 Jun 2026 09:38:05 -0700 Subject: [PATCH 4/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- dotnet/w365-computer-use/sample-agent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/w365-computer-use/sample-agent/README.md b/dotnet/w365-computer-use/sample-agent/README.md index e8c7d9b8..01d16b5c 100644 --- a/dotnet/w365-computer-use/sample-agent/README.md +++ b/dotnet/w365-computer-use/sample-agent/README.md @@ -113,7 +113,7 @@ Use the helper script to get a CUA user token for the MCP server, then set it as ```powershell $tenantId = "" $blueprintClientId = "" - $blueprintClientSecret = "" + $blueprintClientSecret = Read-Host -Prompt "Agent Blueprint client secret" $agentClientId = "" $agentUpn = "" From a5476971057f3919b456d27c712b25d5fc09890b Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Thu, 18 Jun 2026 09:38:40 -0700 Subject: [PATCH 5/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- dotnet/w365-computer-use/sample-agent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/w365-computer-use/sample-agent/README.md b/dotnet/w365-computer-use/sample-agent/README.md index 01d16b5c..3ded9fcb 100644 --- a/dotnet/w365-computer-use/sample-agent/README.md +++ b/dotnet/w365-computer-use/sample-agent/README.md @@ -129,7 +129,7 @@ Use the helper script to get a CUA user token for the MCP server, then set it as `-SetBearerToken` assigns the generated token to `$env:BEARER_TOKEN` for the current PowerShell process and writes an informational message. To use a different token audience, pass `-Scope ""`; by default the script requests `da81128c-e5b5-4f9e-8d89-50d906f107c5/.default`. -The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. The script writes only the access token to stdout, so it can be assigned directly to `$env:BEARER_TOKEN`. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. +The script requests scopes for the Windows 365 for Agents MCP server. Ensure the resulting token has the permissions required to list and invoke tools in your tenant. The script writes only the access token to stdout, so it can be assigned directly to `$env:BEARER_TOKEN`. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. #### Optional: Get a Microsoft Graph token for OneDrive screenshots From 81bb3cdb6b6f5a6fca2d0a71bf43aadd43b7b9be Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Thu, 18 Jun 2026 09:44:40 -0700 Subject: [PATCH 6/8] fix: address W365 token review comments Update the local CUA token helper and README guidance to address PR feedback around token output, secret handling, scope wording, and model configuration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../w365-computer-use/sample-agent/README.md | 13 ++++++------ .../scripts/Get-CuaAgentUserToken.ps1 | 20 ++++++++++--------- 2 files changed, 18 insertions(+), 15 deletions(-) diff --git a/dotnet/w365-computer-use/sample-agent/README.md b/dotnet/w365-computer-use/sample-agent/README.md index 3ded9fcb..9e01f4fe 100644 --- a/dotnet/w365-computer-use/sample-agent/README.md +++ b/dotnet/w365-computer-use/sample-agent/README.md @@ -44,7 +44,7 @@ Response to User - `computer-use-preview` or `gpt-5.4` / `gpt-5.4-mini` - [Request access to gpt-5.4](https://aka.ms/OAI/gpt54access) if needed - Access to the W365 Computer Use MCP server (via [Agent 365 MCP Platform](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/)) -- A bearer token with `McpServers.W365ComputerUse.All` scope +- A bearer token with `Tools.ListInvoke.All` scope ## Setup @@ -113,14 +113,15 @@ Use the helper script to get a CUA user token for the MCP server, then set it as ```powershell $tenantId = "" $blueprintClientId = "" - $blueprintClientSecret = Read-Host -Prompt "Agent Blueprint client secret" + $blueprintClientSecret = Read-Host "Agent Blueprint client secret" -AsSecureString + $blueprintClientSecretPlainText = [System.Net.NetworkCredential]::new("", $blueprintClientSecret).Password $agentClientId = "" $agentUpn = "" .\scripts\Get-CuaAgentUserToken.ps1 ` -TenantId $tenantId ` -AgentBlueprintClientId $blueprintClientId ` - -AgentBlueprintClientSecret $blueprintClientSecret ` + -AgentBlueprintClientSecret $blueprintClientSecretPlainText ` -AgentClientId $agentClientId ` -AgentUsername $agentUpn ` -SetBearerToken ` @@ -129,7 +130,7 @@ Use the helper script to get a CUA user token for the MCP server, then set it as `-SetBearerToken` assigns the generated token to `$env:BEARER_TOKEN` for the current PowerShell process and writes an informational message. To use a different token audience, pass `-Scope ""`; by default the script requests `da81128c-e5b5-4f9e-8d89-50d906f107c5/.default`. -The script requests scopes for the Windows 365 for Agents MCP server. Ensure the resulting token has the permissions required to list and invoke tools in your tenant. The script writes only the access token to stdout, so it can be assigned directly to `$env:BEARER_TOKEN`. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. +The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. Without `-SetBearerToken`, the script writes only the access token to stdout, so it can be assigned directly to `$env:BEARER_TOKEN`. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. #### Optional: Get a Microsoft Graph token for OneDrive screenshots @@ -174,7 +175,7 @@ dotnet run | `AIServices:Provider` | Model provider | `AzureOpenAI` | | `AIServices:AzureOpenAI:Endpoint` | Azure OpenAI resource URL | - | | `AIServices:AzureOpenAI:ApiKey` | API key | - | -| `AIServices:AzureOpenAI:DeploymentName` | Deployment name (for deployment-based URLs) | `computer-use-preview` | +| `AIServices:AzureOpenAI:DeploymentName` | Backward-compatible model identifier fallback when `ModelName` is not set | `computer-use-preview` | | `AIServices:AzureOpenAI:ModelName` | Model name (for model-based URLs, e.g., `gpt-5.4-mini`) | - | | `McpServer:Url` | MCP server URL (dev only; omit for production) | - | | `W365:GatewayUrl` | W365 Computer Use MCP gateway URL (production) | `https://agent365.svc.cloud.microsoft/agents/servers/mcp_W365ComputerUse` | @@ -184,7 +185,7 @@ dotnet run | `Screenshots:LocalPath` | Local path to save screenshots | `./Screenshots` | | `Screenshots:OneDriveFolder` | OneDrive folder for screenshot upload | `CUA-Sessions` | | `Screenshots:OneDriveUserId` | UPN/email to upload screenshots to a specific user's OneDrive (instead of token owner) | - | -| `BEARER_TOKEN` (env var) | MCP Platform token with `McpServers.W365ComputerUse.All` scope (dev only) | - | +| `BEARER_TOKEN` (env var) | MCP Platform token with `Tools.ListInvoke.All` scope (dev only) | - | | `GRAPH_TOKEN` (env var) | Graph API token with `Files.ReadWrite` scope for OneDrive upload (dev only) | - | ## Supported Models diff --git a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 index 8b33202c..240e1d19 100644 --- a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 +++ b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 @@ -8,24 +8,24 @@ Acquires a CUA user access token for an agent using the Entra user_fic flow. .DESCRIPTION Requests an application token, exchanges it for an agent identity token, and then requests a CUA user token from Microsoft Entra ID. By default, the script writes -only the final CUA access token to stdout. Use -SetBearerToken to also assign -the token to $env:BEARER_TOKEN in the current PowerShell process. Use -ShowOid +only the final CUA access token to stdout. Use -SetBearerToken to assign +the token to $env:BEARER_TOKEN in the current PowerShell process instead. Use -ShowOid to decode the final token payload and write the oid claim to the information stream. .EXAMPLE -.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "secret" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" +.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" Writes only the final CUA access token to stdout. .EXAMPLE -.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "secret" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -SetBearerToken -InformationAction Continue +.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -SetBearerToken -InformationAction Continue -Writes the final CUA access token to stdout and assigns it to $env:BEARER_TOKEN -in the current PowerShell process. +Assigns the final CUA access token to $env:BEARER_TOKEN in the current +PowerShell process. .EXAMPLE -.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "secret" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -ShowOid -InformationAction Continue +.\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -ShowOid -InformationAction Continue Writes the final CUA access token to stdout and writes the token oid claim to the information stream. #> @@ -93,6 +93,7 @@ function ConvertFrom-Base64Url { $bytes = [Convert]::FromBase64String($base64) return [System.Text.Encoding]::UTF8.GetString($bytes) +} function Write-OidInformation { param( @@ -184,5 +185,6 @@ if ($SetBearerToken) { $env:BEARER_TOKEN = $cuaAccessToken Write-Information "Set `$env:BEARER_TOKEN for the current PowerShell process." } - -Write-Output $cuaAccessToken \ No newline at end of file +else { + Write-Output $cuaAccessToken +} From e5b86d85b571933ae3fa8b2c452c2a9262496559 Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Thu, 18 Jun 2026 10:00:42 -0700 Subject: [PATCH 7/8] fix: avoid printing CUA token Default the helper to set BEARER_TOKEN and remove token stdout guidance/output paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/w365-computer-use/sample-agent/README.md | 5 ++--- .../scripts/Get-CuaAgentUserToken.ps1 | 16 +++++++++------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/dotnet/w365-computer-use/sample-agent/README.md b/dotnet/w365-computer-use/sample-agent/README.md index 9e01f4fe..d41503fc 100644 --- a/dotnet/w365-computer-use/sample-agent/README.md +++ b/dotnet/w365-computer-use/sample-agent/README.md @@ -124,13 +124,12 @@ Use the helper script to get a CUA user token for the MCP server, then set it as -AgentBlueprintClientSecret $blueprintClientSecretPlainText ` -AgentClientId $agentClientId ` -AgentUsername $agentUpn ` - -SetBearerToken ` -InformationAction Continue ``` - `-SetBearerToken` assigns the generated token to `$env:BEARER_TOKEN` for the current PowerShell process and writes an informational message. To use a different token audience, pass `-Scope ""`; by default the script requests `da81128c-e5b5-4f9e-8d89-50d906f107c5/.default`. + The script assigns the generated token to `$env:BEARER_TOKEN` for the current PowerShell process and writes an informational message. To use a different token audience, pass `-Scope ""`; by default the script requests `da81128c-e5b5-4f9e-8d89-50d906f107c5/.default`. -The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. Without `-SetBearerToken`, the script writes only the access token to stdout, so it can be assigned directly to `$env:BEARER_TOKEN`. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. +The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. The script does not write the access token to stdout. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. #### Optional: Get a Microsoft Graph token for OneDrive screenshots diff --git a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 index 240e1d19..b67f01de 100644 --- a/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 +++ b/dotnet/w365-computer-use/sample-agent/scripts/Get-CuaAgentUserToken.ps1 @@ -7,16 +7,17 @@ Acquires a CUA user access token for an agent using the Entra user_fic flow. .DESCRIPTION Requests an application token, exchanges it for an agent identity token, and then -requests a CUA user token from Microsoft Entra ID. By default, the script writes -only the final CUA access token to stdout. Use -SetBearerToken to assign -the token to $env:BEARER_TOKEN in the current PowerShell process instead. Use -ShowOid +requests a CUA user token from Microsoft Entra ID. By default, the script assigns +the final CUA access token to $env:BEARER_TOKEN in the current PowerShell +process. Use -ShowOid to decode the final token payload and write the oid claim to the information stream. .EXAMPLE .\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -Writes only the final CUA access token to stdout. +Assigns the final CUA access token to $env:BEARER_TOKEN in the current +PowerShell process. .EXAMPLE .\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -SetBearerToken -InformationAction Continue @@ -27,7 +28,8 @@ PowerShell process. .EXAMPLE .\Get-CuaAgentUserToken.ps1 -TenantId "contoso.onmicrosoft.com" -AgentBlueprintClientId "00000000-0000-0000-0000-000000000000" -AgentBlueprintClientSecret "" -AgentClientId "11111111-1111-1111-1111-111111111111" -AgentUsername "user@contoso.com" -ShowOid -InformationAction Continue -Writes the final CUA access token to stdout and writes the token oid claim to the information stream. +Assigns the final CUA access token to $env:BEARER_TOKEN and writes the token +oid claim to the information stream. #> [CmdletBinding()] param( @@ -43,7 +45,7 @@ param( [string]$AgentUsername, [string]$AuthorityHost = "https://login.microsoftonline.com", [string]$Scope = "da81128c-e5b5-4f9e-8d89-50d906f107c5/.default", - [switch]$SetBearerToken, + [switch]$SetBearerToken = $true, [switch]$ShowOid ) @@ -186,5 +188,5 @@ if ($SetBearerToken) { Write-Information "Set `$env:BEARER_TOKEN for the current PowerShell process." } else { - Write-Output $cuaAccessToken + Write-Information "Token acquired. `$env:BEARER_TOKEN was not set because -SetBearerToken was false." } From 7743799f29304f3e19ced2f2a241ed8890e9762f Mon Sep 17 00:00:00 2001 From: Denzel Pfeifer Date: Thu, 18 Jun 2026 10:18:04 -0700 Subject: [PATCH 8/8] docs: remove obsolete token output note Remove README wording that no longer fits the token helper behavior after defaulting to BEARER_TOKEN assignment. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/w365-computer-use/sample-agent/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dotnet/w365-computer-use/sample-agent/README.md b/dotnet/w365-computer-use/sample-agent/README.md index d41503fc..4f78f143 100644 --- a/dotnet/w365-computer-use/sample-agent/README.md +++ b/dotnet/w365-computer-use/sample-agent/README.md @@ -129,7 +129,7 @@ Use the helper script to get a CUA user token for the MCP server, then set it as The script assigns the generated token to `$env:BEARER_TOKEN` for the current PowerShell process and writes an informational message. To use a different token audience, pass `-Scope ""`; by default the script requests `da81128c-e5b5-4f9e-8d89-50d906f107c5/.default`. -The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. The script does not write the access token to stdout. See the [Agent 365 MCP Platform docs](https://learn.microsoft.com/en-us/microsoft-agent-365/developer/) for details. +The script requests scopes for the Windows 365 for Agents MCP server. For this sample, use the `Tools.ListInvoke.All` scope. #### Optional: Get a Microsoft Graph token for OneDrive screenshots