Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
06b5309
feat: add MCP V1-to-V2 migration support with hardcoded V2 catalog fa…
biswapm Mar 30, 2026
d134dbe
feat: add GetScopesByAudienceAsync and --remove-legacy-scopes for V1/…
biswapm Mar 30, 2026
07f1613
feat: add V1/V2 migration safety guards for add-mcp-servers, permissi…
biswapm Mar 30, 2026
37e6c4c
feat: propagate publisher field through manifest write and list-confi…
biswapm Mar 30, 2026
5d38322
feat: replace hardcoded V2 catalog with live V2 discover endpoint
biswapm Apr 1, 2026
eadac86
Fix 1 — ManifestHelper.cs: Both GetRequiredScopesAsync and GetScopesB…
biswapm Apr 3, 2026
3126b2a
docs: update CHANGELOG for MCP V1/V2 migration and null scope fixes
biswapm Apr 3, 2026
fdeef1b
feat: V2 per-server token writes, default audience fix, and dry-run i…
biswapm Apr 9, 2026
f23eeaa
Renamed Agent365ToolsProdAppId → WorkIQToolsProdAppId which was alrea…
biswapm Apr 9, 2026
f28c282
feat: update SP provisioning script to support MCP V1 and V2 models
biswapm Apr 12, 2026
28fd856
fix: switch ProductionDiscoverEndpointUrl to production endpoint
biswapm Apr 14, 2026
20f2aa4
chore: merge main — resolve conflicts in McpConstants and SP provisio…
biswapm Apr 14, 2026
046f92c
Update scripts/cli/Auth/New-Agent365ToolsServicePrincipalProdPublic.ps1
biswapm Apr 14, 2026
9c1db4d
fixed warning messages
biswapm Apr 14, 2026
7d005cd
Update src/Microsoft.Agents.A365.DevTools.Cli/Helpers/ManifestHelper.cs
biswapm Apr 14, 2026
74a4b60
Update src/Microsoft.Agents.A365.DevTools.Cli/Constants/ConfigConstan…
biswapm Apr 14, 2026
932f97a
Update src/Microsoft.Agents.A365.DevTools.Cli/Helpers/ManifestHelper.cs
biswapm Apr 14, 2026
cec3b19
reverting changes
biswapm Apr 14, 2026
f8f8ae2
fix: pass normalizedContent to WriteCatalog to avoid double normaliza…
Copilot Apr 14, 2026
b6f2c42
fix: address PR review comments on MCP V1-V2 migration
biswapm Apr 16, 2026
7293b15
fix: setup all/admin now use per-audience MCP spec list (V1+V2 support)
biswapm Apr 16, 2026
7dd9d40
fix: route --remove-legacy-scopes confirmation through IConfirmationP…
biswapm Apr 16, 2026
3c06db1
fix: update PermissionsSubcommandTests to pass IConfirmationProvider
biswapm Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- `a365 cleanup azure --dry-run` — preview resources that would be deleted without making any changes or requiring Azure authentication
- `AppServiceAuthRequirementCheck` — validates App Service deployment token before `a365 deploy` begins, catching revoked grants (AADSTS50173) early
- `a365 setup admin` — new command for Global Administrators to complete tenant-wide AllPrincipals OAuth2 permission grants after `a365 setup all` has been run by an Agent ID Admin
- MCP V1/V2 migration support — `a365 setup permissions mcp` and `a365 setup blueprint` now handle mixed manifests containing both V1 (`McpServers.*.All` / ATG audience) and V2 (`Tools.ListInvoke.All` / per-server audience) entries; scopes are written additively to the blueprint so agents on either SDK version continue to work
Comment thread
biswapm marked this conversation as resolved.
- `--remove-legacy-scopes` flag for `a365 setup permissions mcp` — removes shared ATG audience scopes from the blueprint once V2 SDK is confirmed live across all agents
- `a365 develop get-token` now acquires one token per audience when using manifest-based scope resolution — V2 entries receive a token scoped to their specific server AppId, V1 entries continue to use the shared ATG AppId
- `a365 develop get-token` now writes per-server bearer tokens to `.env` (Python/Node.js) and `launchSettings.json` (.NET) — V2 servers are written as `BEARER_TOKEN_<SERVER_NAME>` (e.g. `BEARER_TOKEN_MCP_WORDSERVER`), V1 shared-audience token continues to be written as `BEARER_TOKEN` for backward compatibility; local dev samples can now run correctly with V2 multi-audience manifests without needing agentic auth
- `a365 setup permissions mcp --remove-legacy-scopes --dry-run` now shows both what would be removed (shared ATG audience entries) and what would remain after removal, instead of only showing what would be configured

### Changed
- `New-Agent365ToolsServicePrincipalProdPublic.ps1` updated to support MCP V1 and V2 provisioning — adds `-Mode` (`V1`/`V2`/`All`, default `All`), `-ManifestPath` (auto-extracts V2 per-server AppIds from `ToolingManifest.json`), and `-V2AppIds` (explicit list) parameters; script is now idempotent across all AppIds (re-run safe) and covers the migration period where V1 and V2 servers coexist in the same tenant
- `a365 publish` updates manifest IDs, creates `manifest.zip`, and prints concise upload instructions for Microsoft 365 Admin Center (Agents > All agents > Upload custom agent). Interactive prompts only occur in interactive terminals; redirect stdin to suppress them in scripts.
- `a365 develop list-available` resolves MCP server catalog from the live V2 discover endpoint; `--version` column in `a365 develop list-configured` shows `V1` or `V2` based on scope pattern
- `ToolingManifest.json` duplicate server detection now falls back to `mcpServerName` when `mcpServerUniqueName` is absent, preventing false duplicate errors for older manifest entries

### Fixed
- `A365CreateInstanceRunner` sponsor handling: sponsor is now required (Graph API rejects requests without one) — removed fallback that silently stripped the sponsor on retry, which caused `BadRequest` errors
Expand All @@ -29,6 +38,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
- Linux: MSAL fallback when PowerShell `Connect-MgGraph` fails in non-TTY environments (#309)
- Admin consent polling no longer times out after 180s — blueprint service principal now resolved with correct MSAL token (#309)
- `ConfigFileNotFoundException` now derives from `FileNotFoundException` so existing catch sites continue to work (#309)
- `a365 develop list-available` no longer displays `Required Scope: null` for servers that return a `"null"` string scope from the V2 catalog endpoint
- `a365 develop add-mcp-servers` no longer writes the literal string `"null"` as a scope value in `ToolingManifest.json` when the V2 catalog returns `"scope": "null"` — the field is omitted, allowing correct fallback to name-based scope mapping
- `a365 develop get-token` no longer requests a token with scope `"null"` when a manifest entry has a null scope from the V2 catalog
- `a365 setup permissions mcp` no longer passes a literal `"default"` string as an AAD resourceAppId — Dataverse custom servers (`McpServers.DataverseCustom.All`, `McpServers.Dataverse.All`) with `"audience": "default"` are now bucketed under the shared ATG AppId, the same as missing or `api://` legacy audiences

## [1.1.0] - 2026-02

Expand Down
180 changes: 113 additions & 67 deletions scripts/cli/Auth/New-Agent365ToolsServicePrincipalProdPublic.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,123 @@

<#
.SYNOPSIS
Creates the Agent 365 Tools Service Principal in your tenant (Admin only).
Creates Service Principals for Agent 365 MCP servers in your tenant (Admin only).

.DESCRIPTION
This script creates the Service Principal for Agent 365 Tools in your Microsoft Entra ID tenant.
This is a ONE-TIME operation per tenant that requires admin permissions.

After the Service Principal is created, regular users can create their own app
registrations without needing admin rights.
V1 model: Creates the shared 'Agent 365 Tools' Service Principal
(AppId ea9ffc3e-8a23-4a7d-836d-234d7c7565c1).
All V1 servers share this single resource and use McpServers.*.All scopes.

V2 model: Creates one Service Principal per MCP server using per-server AppIds.
V2 servers use the Tools.ListInvoke.All scope against their own audience GUID.
AppIds are extracted from ToolingManifest.json (-ManifestPath) or passed
directly via -V2AppIds.

Use -Mode All (default) during migration when the tenant may have both V1 and V2 servers.

.PARAMETER Mode
V1 - Provision only the shared V1 ATG Service Principal.
V2 - Provision per-server V2 Service Principals only.
All - Provision both V1 and all V2 servers (default, recommended during migration).

.PARAMETER ManifestPath
Path to ToolingManifest.json. The script reads audience GUIDs where scope equals
'Tools.ListInvoke.All' and creates a Service Principal for each unique V2 AppId found.

.PARAMETER V2AppIds
Explicit list of V2 per-server AppIds. Used when -ManifestPath is not provided.

.EXAMPLE
.\New-Agent365ToolsServicePrincipalProdPublic.ps1
(Creates the V1 SP; V2 is skipped unless -ManifestPath or -V2AppIds are supplied.)

.EXAMPLE
.\New-Agent365ToolsServicePrincipalProdPublic.ps1 -Mode V2 -ManifestPath ".\ToolingManifest.json"

.EXAMPLE
.\New-Agent365ToolsServicePrincipalProdPublic.ps1 -Mode All -ManifestPath ".\ToolingManifest.json"

.EXAMPLE
.\New-Agent365ToolsServicePrincipalProdPublic.ps1 -Mode V2 -V2AppIds @("05879165-0320-489e-b644-f72b33f3edf0")

.NOTES
Requires: Admin permissions to create Service Principals
This script only needs to be run ONCE per tenant.
Requires: Admin permissions to create Service Principals.
This script is safe to re-run — existing Service Principals are skipped, not re-created.
#>

param(
[ValidateSet("V1", "V2", "All")]
[string]$Mode = "All",

# Path to ToolingManifest.json — used to auto-extract V2 per-server AppIds
[string]$ManifestPath = "",

# Explicit V2 per-server AppIds (alternative to -ManifestPath)
[string[]]$V2AppIds = @()
)

$ErrorActionPreference = 'Stop'
Set-StrictMode -Version Latest

$resourceId = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1"
# V1: shared ATG AppId (WorkIQToolsProdAppId) — all V1 servers share this resource
$v1AppId = "ea9ffc3e-8a23-4a7d-836d-234d7c7565c1"

# --- Helper: create Service Principal if it does not already exist ---
function Register-ServicePrincipalIfMissing {
param([string]$AppId, [string]$Label)

Write-Host ""
Write-Host " [$Label] AppId: $AppId" -ForegroundColor Cyan

$existing = Get-MgServicePrincipal -Filter "appId eq '$AppId'" -ErrorAction SilentlyContinue
if ($existing) {
Write-Host " Already exists: $($existing.DisplayName) (SP ID: $($existing.Id))" -ForegroundColor Green
return
}

$sp = New-MgServicePrincipal -BodyParameter @{ AppId = $AppId }
Write-Host " Created: $($sp.DisplayName) (SP ID: $($sp.Id))" -ForegroundColor Green
}

Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Service Principal Creation for the 'Agent 365 Tools' application (Admin Only)" -ForegroundColor Cyan
Write-Host "Service Principal Creation for Agent 365 MCP Servers (Admin Only)" -ForegroundColor Cyan
Write-Host " Mode: $Mode" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Write-Host ""
Write-Host "WARNING: This requires admin permissions!" -ForegroundColor Yellow
Write-Host "WARNING: This only needs to be run ONCE per tenant!" -ForegroundColor Yellow
Write-Host "WARNING: Safe to re-run — existing Service Principals are skipped, not re-created." -ForegroundColor Yellow
Write-Host ""

# Validate prerequisites — install Microsoft.Graph modules if not present
# --- Resolve V2 AppIds ---
$resolvedV2AppIds = @()

if ($Mode -ne "V1") {
if ($ManifestPath -and (Test-Path $ManifestPath)) {
Write-Host "Reading V2 AppIds from manifest: $ManifestPath" -ForegroundColor Cyan
$manifest = Get-Content $ManifestPath -Raw | ConvertFrom-Json
$resolvedV2AppIds = @(
$manifest.mcpServers |
Where-Object { $_.scope -eq "Tools.ListInvoke.All" -and $_.audience -match '(?i)^[0-9a-f]{8}-' } |
Comment thread
biswapm marked this conversation as resolved.
Select-Object -ExpandProperty audience -Unique
)
Write-Host " Found $($resolvedV2AppIds.Count) V2 AppId(s) in manifest." -ForegroundColor Cyan
Write-Host ""
}
elseif ($V2AppIds.Count -gt 0) {
$resolvedV2AppIds = $V2AppIds
}
elseif ($Mode -eq "V2") {
Write-Host "ERROR: -Mode V2 requires -ManifestPath or -V2AppIds." -ForegroundColor Red
exit 1
}
}

# --- Ensure Microsoft.Graph modules are available ---
Write-Host "Checking for Microsoft.Graph module..." -ForegroundColor Cyan
if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Applications)) {
Write-Host "Microsoft.Graph.Applications module not found. Installing..." -ForegroundColor Yellow
Install-Module Microsoft.Graph.Applications -Scope CurrentUser -Force -ErrorAction Stop
}

if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)) {
Write-Host "Microsoft.Graph.Authentication module not found. Installing..." -ForegroundColor Yellow
Install-Module Microsoft.Graph.Authentication -Scope CurrentUser -Force -ErrorAction Stop
Expand All @@ -48,18 +128,16 @@ if (-not (Get-Module -ListAvailable -Name Microsoft.Graph.Authentication)) {
Import-Module Microsoft.Graph.Applications -ErrorAction Stop
Import-Module Microsoft.Graph.Authentication -ErrorAction Stop

# Connect to Microsoft Graph
# --- Connect to Microsoft Graph ---
Write-Host ""
Write-Host "Connecting to Microsoft Graph..." -ForegroundColor Cyan
Write-Host "⚠ You need admin permissions for this operation." -ForegroundColor Yellow
Write-Host ""

try {
# Request admin scope for creating service principals
Connect-MgGraph -Scopes "AppRoleAssignment.ReadWrite.All" -NoWelcome
$context = Get-MgContext
Write-Host "✓ Connected to tenant: $($context.TenantId)" -ForegroundColor Green
$tenantId = $context.TenantId
Write-Host ""
}
catch {
Expand All @@ -68,66 +146,33 @@ catch {
exit 1
}

Write-Host "Checking if Service Principal already exists..." -ForegroundColor Cyan

# --- Provision Service Principals ---
try {
$existingSp = Get-MgServicePrincipal -Filter "appId eq '$resourceId'" -ErrorAction SilentlyContinue

if ($existingSp) {
Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host "✓ SERVICE PRINCIPAL ALREADY EXISTS" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "Details:" -ForegroundColor Cyan
Write-Host " Display Name: $($existingSp.DisplayName)" -ForegroundColor White
Write-Host " App ID: $($existingSp.AppId)" -ForegroundColor White
Write-Host " Service Principal ID: $($existingSp.Id)" -ForegroundColor White
Write-Host ""
Write-Host "✓ No action needed - Service Principal is already configured!" -ForegroundColor Green
Write-Host ""

Disconnect-MgGraph | Out-Null
exit 0
}

Write-Host "✓ Service Principal does not exist - proceeding with creation..." -ForegroundColor Yellow
Write-Host ""
}
catch {
Write-Host "⚠ Warning: Could not check for existing Service Principal" -ForegroundColor Yellow
Write-Host " Proceeding with creation attempt..." -ForegroundColor Yellow
Write-Host ""
}
Write-Host "Provisioning Service Principals..." -ForegroundColor Cyan

Write-Host "Creating Service Principal..." -ForegroundColor Cyan
# V1: shared ATG Service Principal
if ($Mode -eq "V1" -or $Mode -eq "All") {
Register-ServicePrincipalIfMissing -AppId $v1AppId -Label "V1 Shared ATG"
}

try {
# Create service principal for the resource app
$spParams = @{
AppId = $resourceId
# V2: per-server Service Principals
if (($Mode -eq "V2" -or $Mode -eq "All") -and $resolvedV2AppIds.Count -gt 0) {
foreach ($appId in $resolvedV2AppIds) {
Register-ServicePrincipalIfMissing -AppId $appId -Label "V2 Per-Server"
}
}
elseif ($Mode -eq "All" -and $resolvedV2AppIds.Count -eq 0) {
Write-Host ""
Write-Host " V2 provisioning skipped — no V2 AppIds found." -ForegroundColor Yellow
Write-Host " Provide -ManifestPath or -V2AppIds to provision V2 servers." -ForegroundColor Yellow
}

$resourceSp = New-MgServicePrincipal -BodyParameter $spParams

Write-Host ""
Write-Host "========================================" -ForegroundColor Green
Write-Host "✓ SERVICE PRINCIPAL CREATED SUCCESSFULLY!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "Details:" -ForegroundColor Cyan
Write-Host " Display Name: $($resourceSp.DisplayName)" -ForegroundColor White
Write-Host " App ID: $($resourceSp.AppId)" -ForegroundColor White
Write-Host " Service Principal ID: $($resourceSp.Id)" -ForegroundColor White
Write-Host " Tenant ID: $tenantId" -ForegroundColor White
Write-Host ""
}
catch {
Write-Host ""
Write-Host "✗ Failed to create Service Principal" -ForegroundColor Red
Write-Host $_.Exception.Message -ForegroundColor Red
Write-Host ""

if ($_.Exception.Message -like "*Insufficient privileges*" -or $_.Exception.Message -like "*Authorization*") {
Write-Host "⚠ This error usually means you don't have admin permissions." -ForegroundColor Yellow
Write-Host ""
Expand All @@ -137,13 +182,14 @@ catch {
Write-Host ""
Write-Host "Please contact your Microsoft Entra ID administrator to run this script." -ForegroundColor Yellow
}

Disconnect-MgGraph | Out-Null
exit 1
}

Disconnect-MgGraph | Out-Null

Write-Host ""
Write-Host "========================================" -ForegroundColor Cyan
Write-Host "Setup Complete!" -ForegroundColor Cyan
Write-Host "========================================" -ForegroundColor Cyan
Expand Down
Loading
Loading