You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It reports which agent instances of the blueprint are missing Tools.ListInvoke.All for the MCP server, then prompts for which ones to grant -- all, a comma-separated list of the numbers shown, or Enter to skip. --yes grants to every listed instance without prompting; --dry-run reports and exits without granting; when stdin is redirected the command reports and exits without granting, since there is nobody to prompt.
The MCP server name maps to an Entra application by appending - BYO (ext_Learn1 resolves ext_Learn1 - BYO); that application's service principal is the grant resource. The grant is a tenant-wide AllPrincipals delegated oauth2PermissionGrants entry.
An earlier revision of this branch also exposed --agent-serviceprincipal-id to grant one identity directly. It was removed: the interactive selection already covers picking individual instances, and a second way to name a target only widened the surface.
Blueprint discovery
Users generally do not know the blueprint GUID. First-party blueprint names and IDs are rendered from AgentBlueprintCatalog into both the --agent-blueprint-id help text and the error shown when the value is malformed, so a failed invocation tells you what to pass:
ERROR: --agent-blueprint-id is required and must be a GUID.
ERROR: First-party blueprints:
ERROR: Sales Development Agent eae28989-4f01-479b-8072-22902e554780
Both surfaces read the same constant, so adding a blueprint updates them together. The option is deliberately not marked IsRequired, so the handler -- not the parser -- reports a missing value and can print this catalog. An earlier revision added a separate list-agent-blueprints command; it was removed in favour of this, since requiring a second command to learn a value the failing command could print is worse for the one case that matters.
Device code authentication
--device-code covers terminals where the Windows WAM broker cannot show a dialog (embedded, SSH, CI). Making that work required fixing three separate bugs:
MicrosoftGraphTokenProvider shelled out to Connect-MgGraph when no client app was configured. Verified experimentally that Connect-MgGraph -UseDeviceCode cannot work as a child process with redirected stdio -- it produces no output, exits 0, and leaves no context. Device code now resolves to in-process MSAL.
That MSAL path needed a client app preauthorized for Graph delegated scopes. PowershellClientId is not, and returned AADSTS65002; it now uses GraphPowershellClientId (14d82eec-...), the app Connect-MgGraph itself authenticates as.
That path had no persistent MSAL account to acquire silently against, so it re-prompted. It now routes through MsalBrowserCredential with useDeviceCode: true, which shares the OS-protected MSAL cache. AuthenticationService.CreateDeviceCodeCredential is deliberately left untouched, so setup on Linux/macOS/WSL, the browser-unsupported fallback, and develop get-token --device-code are unchanged.
The CLI authenticates two different client apps -- its base identity for directory lookups and the Graph command-line app for scoped calls -- and device code cannot SSO across distinct client apps. MSAL partitions its account cache per client ID, so the cached account is now resolved under the client actually being authenticated rather than a hardcoded one; the second app reuses that account instead of starting a fresh sign-in. A device-code run prompts once. Collapsing the two apps entirely would mean changing the CLI's base client app, which affects every command and does not belong here.
Testing
2088 unit tests pass. ConsoleHelper gains an input-redirection test seam alongside the existing ReadLine one, so the interactive prompt path is covered under the test runner (which always redirects stdin) rather than only its redirected fallback.
Verified end to end against a live tenant: resolved ext_Learn1 - BYO, listed two agent instances of a real blueprint, granted the scope to the one missing it, and confirmed the grant was created. Re-verified with tenant auto-detection (no --tenant-id) and the normal WAM path.
Also verified from the packed .nupkg installed as a tool: --device-code --dry-run completed with a single sign-in prompt, resolved the server, enumerated both agent instances, reported 0 missing, and exited 0.
Review round
Changes made in response to review, beyond the two gaps closed below:
A failed oauth2PermissionGrants read is no longer indistinguishable from an empty one, so --yes cannot grant on the strength of a lookup that never succeeded.
An ambiguous MCP server name (more than one application sharing the - BYO display name) no longer silently picks the first match: every match is listed and the user chooses. With input redirected there is nobody to ask, so it is an error.
--mcp-server-name is validated against the shared input allowlist before it reaches the Graph filter.
An explicitly blank --tenant-id is an error rather than a silent fallback to the Azure CLI context.
The device-code path no longer falls back to Connect-MgGraph and passes GraphPowershellClientId for Graph token acquisition.
Second review round
The cached login hint was looked up under a hardcoded client ID. MSAL partitions its account cache per client, so a device-code account could never be found. The client ID is now threaded through, and the token request and hint lookup derive from one property so they cannot drift apart.
A Graph authorization failure while reading applications was reported as "no application named X was found", sending the user off to create an application that may already exist. A failed read and an absent application are now distinguished.
An interactive selection that could not be parsed logged an error but exited 0. It now exits 1, so a typo cannot pass silently in a script.
Added HTTP-level coverage for the multi-match, read-failure, and empty-result application lookups, for the login-hint client wiring, and for the invalid-selection exit code.
Third review round: scoping to this command
Reviewers asked that this PR not change infrastructure other commands depend on. Everything flagged has been pulled back so the new behaviour is confined to grant-agents-access:
AuthenticationService.CreateDeviceCodeCredential is fully reverted, including the logger block that prints the device code prompt. This command never needed it -- its device-code path runs through GraphApiService -> MicrosoftGraphTokenProvider.GetMgGraphAccessTokenAsync(..., useDeviceCode, ...), which already took useDeviceCode. setup on Linux/macOS/WSL, the browser-unsupported fallback, and develop get-token --device-code are unchanged.
The mutable UseDeviceCodeAuthentication flag is off the GraphApiService singleton. useDeviceCode is an optional per-call parameter defaulting to false; the flag lives on McpServerPermissionService, which passes it explicitly. No later Graph call in the process can be affected.
The stricter consentType grant lookup is opt-in via requireMatchingConsentType, defaulting to false, so setup and create-instance are byte-for-byte unchanged. A test pins the unchanged default alongside the opt-in one.
FindApplicationByDisplayNameAsync is restored to its original standalone $top=1 query rather than delegating to the new multi-match lookup, so cleanup, ConfigService and SetupHelpers are unaffected.
Two correctness items were also addressed:
The multi-match lookup pages through @odata.nextLink with no $top, so every application sharing the name is returned and offered to the user to choose from.
The application lookup now returns why it failed instead of re-acquiring a token to decide which message to show, removing a path where a cancelled sign-in could prompt a second time.
Notes for reviewers
No new client app identity is introduced for the default path; this command reuses the CLI's existing base identity.
It reads the tenant from --tenant-id or Azure CLI context, and does not consult a365.config.json. That matches publish/register/setup, but differs from develop add-permissions/get-token, which do read config. Happy to align if reviewers prefer.
Pre-existing gap, deliberately not fixed here: the AllPrincipals grant lookup in CreateOrUpdateOauth2PermissionGrantCoreAsync filters on clientId + resourceId only and PATCHes arr[0], so an existing Principal grant for the same pair is patched and the tenant-wide grant is never created. Reviewers asked that this land separately with tests on the setup path, so the stricter consentType eq 'AllPrincipals' lookup is opt-in via requireMatchingConsentType, defaulting to false. setup and create-instance keep their exact current behaviour; grant-agents-access is the only caller opting in. A follow-up PR will fix the shared path.
Pre-existing gap, now fixed after review: DevelopMcpCommandTests enforced that every develop-mcp subcommand accepts --dry-run, but built the command with a null permission service, so this subcommand was never registered and escaped the check. The test now supplies a permission service, and the command supports --dry-run.
Not verified by me: the default (non---device-code) Windows sign-in path on a clean machine -- my box hits an unrelated WAM crash -- and a setup all --authmode obo run against a scratch agent, which would exercise the shared grant change above.
Adds two subcommands under a365 develop-mcp:
- list-agent-instances reports agent instances of a blueprint missing
Tools.ListInvoke.All for a BYO MCP server and offers to grant it.
- grant-mcpserver-permissions creates the AllPrincipals delegated grant
for a single agent identity.
The MCP server name resolves to the Entra application '{name} - BYO';
that application's service principal is the grant resource.
Co-authored-by: Copilot App <[email protected]>
A failed Graph sign-in made FindApplicationByDisplayNameAsync return null,
which was reported as "No Entra application named '<name> - BYO' was found"
and pointed the user at creating an app that may already exist.
Co-authored-by: Copilot App <[email protected]>
Adds 'develop-mcp list-agent-blueprints', which lists Microsoft's
first-party agent blueprint names alongside their IDs. Users had no way
to find the GUID that --agent-blueprint-id requires. The invalid-GUID
error and the option help both point at the new command, so the hint
appears at the moment the user is stuck.
Also makes --device-code work on the two MCP permission commands:
- Connect-MgGraph cannot render a device code prompt from a child
process with redirected I/O, so it silently produced no context.
Device code now runs in-process via MSAL as the Graph command-line
app, which is preauthorized for Graph delegated scopes (the Azure
PowerShell app is rejected with AADSTS65002).
- Device code credentials were rebuilt per token request with no
AuthenticationRecord, so the persisted cache could be written but
never silently read, prompting on every call. They now route through
MsalBrowserCredential, which attempts silent acquisition first.
- The MCP server lookup no longer acquires a pre-flight token on the
success path.
Co-authored-by: Copilot App <[email protected]>
Replaces the standalone list-agent-blueprints command with the two
surfaces users already reach: the --agent-blueprint-id help text and
the error shown when the option is missing or not a GUID. With a single
first-party blueprint, a dedicated command asked users to run something
else to learn a value the failing command could simply print.
Both surfaces render from AgentBlueprintCatalog, so adding a blueprint
updates help and error output together and neither can go stale.
The service principal ID option deliberately does not list blueprints:
it takes a tenant-specific object ID, so those values are never valid
there.
Co-authored-by: Copilot App <[email protected]>
The summary labels said "Scope" and "Instances", which read ambiguously
next to the MCP server line, and the grant prompt quoted the scope with
no indication of which server it applied to. Name both in full.
The prompt uses the server name the caller passed rather than the
resolved '<name> - BYO' display name, so it echoes what they typed.
Co-authored-by: Copilot App <[email protected]>
- list-agent-instances exited 0 when a blueprint had no agent instances,
so a script could not distinguish "nothing to do" from "wrong ID".
- ResolveMsalClientAppId carried two stacked doc comments, leaving
AcquireGraphTokenViaMsalAsync undocumented.
- A comment named the wrong fallback client app.
- AgentBlueprintCatalog.TryGetDisplayName had no production caller.
Co-authored-by: Copilot App <[email protected]>
An invalid numeric selection logs an error and returns an empty list, but the caller treats selected.Count == 0 as a normal decline and leaves the exit code at 0. Scripts cannot detect invalid input; propagate a failure result from ResolveSelection and set context.ExitCode = 1 for this branch.
The predicate ignores consentType. A Principal grant for the same client and resource that contains Tools.ListInvoke.All is not the tenant-wide AllPrincipals grant promised by these commands, but it will be reported as already granted and skipped. Require consentType to equal AllPrincipals here.
This issue also appears on line 132 of the same file.
The command's primary effect is granting, not listing -- the listing is
a confirmation step before the grant. Leads the description with the
grant for the same reason.
Co-authored-by: Copilot App <[email protected]>
One command now covers both targets: --agent-blueprint-id reviews every
agent instance of a blueprint, --agent-serviceprincipal-id grants one
identity directly. Exactly one must be supplied.
Co-authored-by: Copilot App <[email protected]>
These newly registered mutating subcommands have no --dry-run, while the established develop-mcp contract requires every subcommand to expose it (DevelopMcpCommandTests.cs:185-195). The existing test constructs this command with a null permission service, so it skips these registrations and cannot catch the production mismatch; add the safety option/handling or update the contract and tests intentionally.
This branch logs an invalid interactive selection and returns an empty list, but the caller treats every empty selection as a normal decline and returns without setting ExitCode = 1. A typo such as 99 therefore reports success to scripts even though no requested grant was performed; propagate an invalid-selection result separately from an intentional empty response.
Device-code flag is lost during tenant-mismatch retry
The new useDeviceCode flag is propagated for the initial MSAL acquisition, but the tenant-mismatch retry later calls AcquireGraphTokenViaMsalAsync without that argument. A device-code invocation can therefore retry through browser/WAM (and lose the Graph PowerShell client fallback) instead of recovering; thread the flag through the retry path as well.
The status check matches only resource and scope, not consentType. A Principal grant for the same agent/resource is therefore reported as satisfying the command's required tenant-wide AllPrincipals grant, so the command can skip creating the grant it promises to manage.
This issue also appears on line 132 of the same file.
An invalid interactive selection is logged here but returned as []; the caller treats any empty result as a normal skip at lines 181-184 and leaves the exit code at 0. Thus 1,999 reports an error while scripting sees success. Preserve an invalid-selection result separately (for example, return null) and set context.ExitCode = 1 for it.
The new device-code switch only changes useInteractiveBrowser; this ambient path still lets AuthenticationService default the client ID to PowershellClientId. The resource lookup uses this path when no configured client app exists, so --device-code can fail with AADSTS65002 before the token-provider substitution to GraphPowershellClientId is reached. Pass the Graph PowerShell client ID for this device-code path or route ambient calls through the token provider.
Application lookup failures are reported as not found
FindApplicationByDisplayNameAsync returns null for HTTP, network, and response errors as well as for a missing application. This follow-up token check can still succeed from cache, causing a transient Graph failure to be reported as "No Entra application" and misleading the caller. Use a response-bearing lookup that distinguishes not-found from failure.
Drops --agent-serviceprincipal-id. The command lists the blueprint's
agent instances missing the MCP server scope and the user picks which
to grant, so there is no second way to name a target.
Adds a test seam for input redirection so the prompt path can be
covered under the test runner, which always redirects stdin.
Co-authored-by: Copilot App <[email protected]>
An out-of-range or non-numeric response is logged as an error but returns an empty selection; the handler treats an empty selection as a deliberate skip and exits with code 0. Invalid interactive input must produce a non-zero exit code so scripts can distinguish it from Enter or redirected-input no-ops. Return a validity indicator (or nullable selection) and set context.ExitCode = 1 in the handler when the selection is invalid.
Preserve application lookup failures instead of reporting not found
FindApplicationByDisplayNameAsync returns null for both 'not found' and HTTP, network, or response failures. A second token acquisition only proves credentials can be obtained, so a failed query with a cached token is reported as 'No Entra application ... was found', potentially sending users to create an app that already exists. Use a status-bearing lookup result or preserve the original failure reason.
- Add --dry-run so the command can report missing grants without writing.
- Let the handler reject a missing --agent-blueprint-id so the blueprint
catalog is printed instead of a bare parser error.
- Treat an explicitly blank --tenant-id as an error rather than silently
falling back to the Azure CLI context.
- Stop falling back to Connect-MgGraph when --device-code is requested;
the PowerShell prompt cannot work with redirected stdio.
- Distinguish a failed oauth2PermissionGrants read from an empty one so the
command no longer reports every instance as missing after a Graph failure.
- Fail when more than one application shares the BYO MCP server display name
instead of silently picking the first match.
- Validate --mcp-server-name against the shared input allowlist.
Co-authored-by: Copilot App <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
Requesting changes.
CI hasn't run the build/test workflow on this PR, only license/cla. Please get the .NET workflow to run; the new tests aren't validated otherwise.
Beyond the new command, this changes shared auth/Graph behavior: the device code credential used by every command (AuthenticationService) and the AllPrincipals grant lookup used by setup / create-instance. Both look like legitimate fixes, but I'd like them in a separate PR so they can be reviewed, tested and reverted on their own. If they stay here, the title/description should call them out and they need test coverage on the setup/create-instance path.
All inline comments below need addressing before merge.
The interactive prompt split on ',' with RemoveEmptyEntries, so input like
"," or "1," produced zero tokens and was treated as "skip everything" with
exit code 0. A typo therefore left agents without the permission while the
command reported success. Empty tokens are now rejected with exit code 1.
The sign-in-failure test mocked the lookup as an empty list, which takes the
"application not found" branch and never reaches the token probe, so it would
have passed with the auth-failure handling deleted. Both resolve-failure tests
now mock a failed read and assert on the logged diagnostic.
Corrects the CHANGELOG entry for the consentType fix: setup and
create-instance create the missing tenant-wide grant rather than reporting a
failure.
Co-authored-by: Copilot App <[email protected]>
TryFindApplicationAppIdsByDisplayNameAsync promises null when the lookup cannot produce a trustworthy result, but a successful response with no array-valued value is converted to an empty list. The caller then reports that the application does not exist, even though the response was malformed. Treat a missing or non-array value as a failed lookup instead.
This issue also appears on line 1301 of the same file.
Preserve Graph lookup failure details instead of reporting no principal
LookupServicePrincipalByAppIdAsync returns null both for a successful empty result and for authentication, HTTP, transport, or response-format failures (GraphApiService.cs:652-664). Consequently a 403 or outage here is incorrectly reported as “has no service principal.” Use the existing status-bearing LookupServicePrincipalByAppIdWithResponseAsync; log its failure reason when IsSuccess is false, and reserve this message for a successful empty result.
Handle all Graph grant pages before checking permissions
This status check relies on TryGetOauth2PermissionGrantsAsync, which reads only the first Graph collection page and ignores @odata.nextLink (GraphApiService.cs:1308-1326). An agent identity with enough grants can therefore have this server's AllPrincipals grant on a later page and be incorrectly reported as missing, especially in --dry-run. Query by client/resource/consent type or follow all pages before computing HasScope.
Address review feedback that the PR altered shared infrastructure used by
setup, create-instance, cleanup and develop get-token.
- Revert CreateDeviceCodeCredential to its original DeviceCodeCredential
implementation. The new command never used it; it reaches device code
through MicrosoftGraphTokenProvider, which already took useDeviceCode.
- Replace the mutable UseDeviceCodeAuthentication flag on the GraphApiService
singleton with an optional per-call parameter. The flag now lives on
McpServerPermissionService, which passes it explicitly.
- Make the oauth2PermissionGrants consentType lookup opt-in via
requireMatchingConsentType. Existing callers keep the original behaviour;
only grant-agents-access opts in. The shared setup-path fix lands separately.
- Restore FindApplicationByDisplayNameAsync to its original standalone
\=1 query instead of delegating to the new multi-match lookup.
- Query one past ApplicationDisplayNameMatchLimit so an over-limit result is
reported as ambiguous rather than silently truncated.
- Return why an application lookup failed instead of re-acquiring a token to
decide which error to show, which could prompt the user a second time.
Co-authored-by: Copilot App <[email protected]>
The tenant-wide grant fix is now opt-in and no longer alters setup or
create-instance, and the device-code fix applies only to the new command.
Co-authored-by: Copilot App <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
Copilot review overview
🟡 Changes recommended
Device-code routing remains incomplete, client-specific cache resolution is inconsistent, and the shared grant behavior contradicts the documented fix.
Get a fresh assessment by requesting another Copilot review.
GraphGetAsync can return null on an authentication or HTTP failure. This then leaves existingId null and proceeds to POST; if the grant already exists without the desired scope, the duplicate response is treated as success at lines 1296-1302, so the command can print Granted while the scope is still missing. For the new strict mode, abort when the existing-grant read fails.
Distinguish service principal lookup failures from absent results
LookupServicePrincipalByAppIdAsync returns null both for a successful empty result and for authentication, HTTP, or response failures (GraphApiService.cs:659-672), but this path reports every null as “has no service principal.” A permission or transient failure therefore sends users toward fixing a nonexistent registration problem. Use the status-bearing lookup and distinguish lookup failure from an absent service principal.
Drop the \ cap on the MCP server application lookup and page through
@odata.nextLink instead, so no match is hidden from the user.
When more than one application shares the MCP server's display name, list
them all and prompt for which to use rather than failing outright. With
input redirected there is nobody to ask, so that remains an error: display
names are not unique and guessing could grant against the wrong server.
Co-authored-by: Copilot App <[email protected]>
This new status lookup reads only the first collection page and ignores @odata.nextLink. If an agent identity has enough delegated grants for the MCP server's grant to appear on a later page, the command reports it as missing and attempts a duplicate grant. Follow all pages before deciding HasScope, as the new application lookup already does.
Distinguish missing service principals from lookup failures
LookupServicePrincipalByAppIdAsync returns null both when no service principal exists and when authentication, transport, or Graph authorization fails. This branch therefore tells users the application definitively has no service principal even when the directory could not be read. Use a response-bearing lookup or otherwise distinguish lookup failure from a successful empty result before emitting this message.
Correct device-code callback message about browser navigation
The device-code callback only prints the verification URL and code; it does not open a browser. Saying it "Opens" the site is misleading, especially for the remote-terminal scenario this option targets. Describe that the user must visit the URL instead.
…e grant state
EnsureGraphHeadersAsync honored useDeviceCode only in the token-provider branch.
The legacy fallback dropped it, so grant-agents-access could launch WAM despite
--device-code whenever CustomClientAppId was unresolved. Existing callers all
pass false, so the forwarded flag is a no-op for them.
A failed grant lookup left existingId null and fell through to POST, where
"Permission entry already exists" is reported as success even though the scope
was never merged. Gated behind abortWhenLookupFails so only the new command
opts in and setup/create-instance keep their current behavior.
Co-authored-by: Copilot App <[email protected]>
This reads only the first Graph page and ignores @odata.nextLink. If an agent identity has enough delegated grants for the target MCP grant to appear on a later page, the command reports it as missing (including in --dry-run) and may attempt an unnecessary grant. Follow all pages, as the application and blueprint lookups do, and return null if any page fails.
Device-code message falsely claims to open the verification URL
The device-code implementation prints the verification URI and code; it does not open a browser. Saying it “Opens” the URL is misleading, especially for the remote-terminal users this option targets. Describe the manual navigation accurately.
The legacy fallback built a fresh Azure.Identity DeviceCodeCredential per
call with no AuthenticationRecord, so every Graph call under --device-code
re-prompted for a new code, and it wrote to a different cache store than the
scoped calls. Route the device-code path through the token provider, which
reads the cache before prompting and shares one store with every other call.
Gated on useDeviceCode, which only the new grant-agents-access path sets, so
all pre-existing callers keep the AuthenticationService path unchanged.
Co-authored-by: Copilot App <[email protected]>
The paging guard can still return the same application ID more than once: the new repeated-nextLink test reads the same app-1 from two pages and currently asserts a count of two. ResolveServerResourceAsync then treats those duplicate IDs as distinct applications, causing a bogus selection prompt or a failure when input is redirected. Deduplicate IDs case-insensitively while accumulating pages, and update the cycle test to expect one ID.
This issue also appears on line 1395 of the same file.
Distinguish unreadable service principals from missing ones
LookupServicePrincipalByAppIdAsync returns null both when the service principal is absent and when the Graph read fails, so this definitive message misreports authorization, network, or authentication failures as a missing service principal. Prefer a response-bearing lookup; at minimum, make this diagnostic acknowledge that the principal may be unreadable.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds one
a365 develop-mcpcommand for granting BYO MCP server permissions to agent identities.It reports which agent instances of the blueprint are missing
Tools.ListInvoke.Allfor the MCP server, then prompts for which ones to grant --all, a comma-separated list of the numbers shown, or Enter to skip.--yesgrants to every listed instance without prompting;--dry-runreports and exits without granting; when stdin is redirected the command reports and exits without granting, since there is nobody to prompt.The MCP server name maps to an Entra application by appending
- BYO(ext_Learn1resolvesext_Learn1 - BYO); that application's service principal is the grant resource. The grant is a tenant-wideAllPrincipalsdelegatedoauth2PermissionGrantsentry.An earlier revision of this branch also exposed
--agent-serviceprincipal-idto grant one identity directly. It was removed: the interactive selection already covers picking individual instances, and a second way to name a target only widened the surface.Blueprint discovery
Users generally do not know the blueprint GUID. First-party blueprint names and IDs are rendered from
AgentBlueprintCataloginto both the--agent-blueprint-idhelp text and the error shown when the value is malformed, so a failed invocation tells you what to pass:Both surfaces read the same constant, so adding a blueprint updates them together. The option is deliberately not marked
IsRequired, so the handler -- not the parser -- reports a missing value and can print this catalog. An earlier revision added a separatelist-agent-blueprintscommand; it was removed in favour of this, since requiring a second command to learn a value the failing command could print is worse for the one case that matters.Device code authentication
--device-codecovers terminals where the Windows WAM broker cannot show a dialog (embedded, SSH, CI). Making that work required fixing three separate bugs:MicrosoftGraphTokenProvidershelled out toConnect-MgGraphwhen no client app was configured. Verified experimentally thatConnect-MgGraph -UseDeviceCodecannot work as a child process with redirected stdio -- it produces no output, exits 0, and leaves no context. Device code now resolves to in-process MSAL.PowershellClientIdis not, and returnedAADSTS65002; it now usesGraphPowershellClientId(14d82eec-...), the appConnect-MgGraphitself authenticates as.MsalBrowserCredentialwithuseDeviceCode: true, which shares the OS-protected MSAL cache.AuthenticationService.CreateDeviceCodeCredentialis deliberately left untouched, sosetupon Linux/macOS/WSL, the browser-unsupported fallback, anddevelop get-token --device-codeare unchanged.The CLI authenticates two different client apps -- its base identity for directory lookups and the Graph command-line app for scoped calls -- and device code cannot SSO across distinct client apps. MSAL partitions its account cache per client ID, so the cached account is now resolved under the client actually being authenticated rather than a hardcoded one; the second app reuses that account instead of starting a fresh sign-in. A device-code run prompts once. Collapsing the two apps entirely would mean changing the CLI's base client app, which affects every command and does not belong here.
Testing
2088 unit tests pass.
ConsoleHelpergains an input-redirection test seam alongside the existingReadLineone, so the interactive prompt path is covered under the test runner (which always redirects stdin) rather than only its redirected fallback.Verified end to end against a live tenant: resolved
ext_Learn1 - BYO, listed two agent instances of a real blueprint, granted the scope to the one missing it, and confirmed the grant was created. Re-verified with tenant auto-detection (no--tenant-id) and the normal WAM path.Also verified from the packed
.nupkginstalled as a tool:--device-code --dry-runcompleted with a single sign-in prompt, resolved the server, enumerated both agent instances, reported0 missing, and exited 0.Review round
Changes made in response to review, beyond the two gaps closed below:
oauth2PermissionGrantsread is no longer indistinguishable from an empty one, so--yescannot grant on the strength of a lookup that never succeeded.- BYOdisplay name) no longer silently picks the first match: every match is listed and the user chooses. With input redirected there is nobody to ask, so it is an error.--mcp-server-nameis validated against the shared input allowlist before it reaches the Graph filter.--tenant-idis an error rather than a silent fallback to the Azure CLI context.Connect-MgGraphand passesGraphPowershellClientIdfor Graph token acquisition.Second review round
Third review round: scoping to this command
Reviewers asked that this PR not change infrastructure other commands depend on. Everything flagged has been pulled back so the new behaviour is confined to
grant-agents-access:AuthenticationService.CreateDeviceCodeCredentialis fully reverted, including the logger block that prints the device code prompt. This command never needed it -- its device-code path runs throughGraphApiService->MicrosoftGraphTokenProvider.GetMgGraphAccessTokenAsync(..., useDeviceCode, ...), which already tookuseDeviceCode.setupon Linux/macOS/WSL, the browser-unsupported fallback, anddevelop get-token --device-codeare unchanged.UseDeviceCodeAuthenticationflag is off theGraphApiServicesingleton.useDeviceCodeis an optional per-call parameter defaulting to false; the flag lives onMcpServerPermissionService, which passes it explicitly. No later Graph call in the process can be affected.consentTypegrant lookup is opt-in viarequireMatchingConsentType, defaulting to false, sosetupandcreate-instanceare byte-for-byte unchanged. A test pins the unchanged default alongside the opt-in one.FindApplicationByDisplayNameAsyncis restored to its original standalone$top=1query rather than delegating to the new multi-match lookup, socleanup,ConfigServiceandSetupHelpersare unaffected.Two correctness items were also addressed:
@odata.nextLinkwith no$top, so every application sharing the name is returned and offered to the user to choose from.Notes for reviewers
--tenant-idor Azure CLI context, and does not consulta365.config.json. That matchespublish/register/setup, but differs fromdevelop add-permissions/get-token, which do read config. Happy to align if reviewers prefer.AllPrincipalsgrant lookup inCreateOrUpdateOauth2PermissionGrantCoreAsyncfilters onclientId+resourceIdonly and PATCHesarr[0], so an existingPrincipalgrant for the same pair is patched and the tenant-wide grant is never created. Reviewers asked that this land separately with tests on the setup path, so the stricterconsentType eq 'AllPrincipals'lookup is opt-in viarequireMatchingConsentType, defaulting to false.setupandcreate-instancekeep their exact current behaviour;grant-agents-accessis the only caller opting in. A follow-up PR will fix the shared path.DevelopMcpCommandTestsenforced that everydevelop-mcpsubcommand accepts--dry-run, but built the command with a null permission service, so this subcommand was never registered and escaped the check. The test now supplies a permission service, and the command supports--dry-run.--device-code) Windows sign-in path on a clean machine -- my box hits an unrelated WAM crash -- and asetup all --authmode oborun against a scratch agent, which would exercise the shared grant change above.