Skip to content

Add get-agent-instances and add-byo-scopes CLI commands#471

Open
ragurubhaarath wants to merge 3 commits into
mainfrom
bhraguru-microsoft-add-byo-scope-cli-commands
Open

Add get-agent-instances and add-byo-scopes CLI commands#471
ragurubhaarath wants to merge 3 commits into
mainfrom
bhraguru-microsoft-add-byo-scope-cli-commands

Conversation

@ragurubhaarath

Copy link
Copy Markdown
Contributor

Summary

Adds two new subcommands to the develop-mcp command group for managing BYO MCP server permissions on agent instances.

New Commands

develop-mcp get-agent-instances

Lists agent instance service principals linked to a given blueprint ID.

Options:

  • --blueprint-id / -b (required) - Agent Identity Blueprint ID (GUID)
  • --tenant-id (optional) - Azure AD tenant ID; auto-detected from az account show if omitted
  • --verbose / -v - Enable verbose logging

develop-mcp add-byo-scopes

Grants oauth2 delegated permissions (user_impersonation) for BYO MCP servers to agent instances.

Options:

  • --server-names / -s (required) - Comma-separated list of BYO server names
  • --blueprint-id / -b (optional) - Blueprint ID to auto-resolve all agent instances
  • --agent-instances / -i (optional) - Comma-separated list of agent instance SP IDs
  • --tenant-id (optional) - Azure AD tenant ID; auto-detected if omitted
  • --dry-run - Show what would be done without executing
  • --verbose / -v - Enable verbose logging

At least one of --blueprint-id or --agent-instances is required.

Other Changes

  • Added GetMcpServerAppIdsByNameAsync to IAgent365ToolingService
  • Added McpServerAppIdsResponse model
  • Updated DevelopMcpCommand.CreateCommand signature
  • CHANGELOG entry added

Testing

  • All 1944 existing tests pass (0 failures)
  • Added tests for both executors and subcommand registration

Add two new subcommands to the develop-mcp command group:

- get-agent-instances: Lists agent instance service principals for a
  given blueprint ID. Accepts --blueprint-id (required), --tenant-id
  (optional, auto-detected from az CLI), and --verbose.

- add-byo-scopes: Grants oauth2 delegated permissions (user_impersonation)
  for BYO MCP servers to agent instances. Resolves the PPMI app ID via
  the new GET /agents/mcpServers/byName/{serverName}/appIds endpoint,
  then creates/updates oauth2PermissionGrants in Microsoft Graph.
  Supports --server-names, --blueprint-id, --agent-instances, --tenant-id,
  --dry-run, and --verbose.

Also adds GetMcpServerAppIdsByNameAsync to IAgent365ToolingService and
its implementation, plus the McpServerAppIdsResponse model.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@ragurubhaarath
ragurubhaarath requested review from a team as code owners July 8, 2026 21:48
Copilot AI review requested due to automatic review settings July 8, 2026 21:48
@github-actions github-actions Bot added the documentation Improvements or additions to documentation label Jul 8, 2026
@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown

⚠️ Deprecation Warning: The deny-licenses option is deprecated for possible removal in the next major release. For more information, see issue 997.

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

The MCP Platform route changed from a path-based to a query-parameter
style: GET /agents/mcpServers/appIds?serverName={serverName}

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds two new develop-mcp subcommands to manage BYO MCP server delegated permissions for agent instances, including a new MCP Platform API call for resolving BYO server PPMI app IDs.

Changes:

  • Adds develop-mcp get-agent-instances and develop-mcp add-byo-scopes subcommands (executors + command registration).
  • Extends IAgent365ToolingService / Agent365ToolingService with GetMcpServerAppIdsByNameAsync and a new response model.
  • Documents the new commands in CHANGELOG.md and wires the new dependencies in Program.cs.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/GetAgentInstancesExecutorTests.cs Adds unit tests for the new executor and subcommand wiring.
src/Tests/Microsoft.Agents.A365.DevTools.Cli.Tests/Commands/AddByoScopesExecutorTests.cs Adds unit tests for validation paths and subcommand wiring for scope grants.
src/Microsoft.Agents.A365.DevTools.Cli/Services/IAgent365ToolingService.cs Adds a new service contract method to resolve BYO server app IDs.
src/Microsoft.Agents.A365.DevTools.Cli/Services/Agent365ToolingService.cs Implements MCP Platform call to resolve BYO server app IDs.
src/Microsoft.Agents.A365.DevTools.Cli/Program.cs Updates DevelopMcpCommand.CreateCommand invocation to pass AgentBlueprintService.
src/Microsoft.Agents.A365.DevTools.Cli/Models/McpServerAppIdsResponse.cs Introduces response model for the new MCP Platform endpoint.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopSubcommands/GetAgentInstancesExecutor.cs Implements executor for listing agent instances by blueprint ID.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopSubcommands/AddByoScopesExecutor.cs Implements executor for granting delegated scopes to agent instances for BYO servers.
src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopMcpCommand.cs Registers the new subcommands and updates command factory signature.
CHANGELOG.md Adds release-note entries for the new commands.

Comment on lines +48 to +57
// Resolve tenant ID
if (string.IsNullOrWhiteSpace(tenantId))
{
tenantId = await ResolveTenantIdAsync(cancellationToken);
if (string.IsNullOrWhiteSpace(tenantId))
{
_logger.LogError("Could not determine tenant ID. Provide --tenant-id or sign in with 'az login'.");
return false;
}
}
Comment on lines +90 to +94
catch (Exception ex)
{
_logger.LogError(ex, "Failed to list agent instances for blueprint {BlueprintId}", blueprintId);
return false;
}
Comment on lines +78 to +87
// Resolve tenant ID
if (string.IsNullOrWhiteSpace(tenantId))
{
tenantId = await ResolveTenantIdAsync(cancellationToken);
if (string.IsNullOrWhiteSpace(tenantId))
{
_logger.LogError("Could not determine tenant ID. Provide --tenant-id or sign in with 'az login'.");
return false;
}
}
Comment on lines +219 to +223
catch (Exception ex)
{
_logger.LogError(ex, "Failed to resolve agent instances for blueprint {BlueprintId}", blueprintId);
return null;
}
Comment on lines +273 to +277
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to resolve service principal for appId {AppId}", appId);
return null;
}
_logger.LogDebug("Acquiring access token for audience: {Audience}", audience);

var loginHint = await AzCliHelper.ResolveLoginHintAsync();
var authToken = await _authService.GetAccessTokenAsync(audience, userId: loginHint);
Comment on lines +913 to +917
catch (Exception ex)
{
_logger.LogError(ex, "Failed to get app IDs for MCP server {ServerName}", serverName);
return null;
}
/// </summary>
/// <param name="serverName">MCP server name</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Response containing the MCP server's PPMI app ID, or null on failure</returns>
Comment on lines +143 to +151
[Fact]
public void CreateCommand_WithBlueprintService_IncludesGetAgentInstances()
{
var command = DevelopMcpCommand.CreateCommand(
_mockLogger, _mockToolingService, agentBlueprintService: _mockBlueprintService);

command.Subcommands.Select(sc => sc.Name).Should().Contain(
"get-agent-instances",
because: "providing the blueprint service should register the get-agent-instances subcommand");
Comment on lines +168 to +179
[Fact]
public void CreateCommand_WithBlueprintAndGraphServices_IncludesAddByoScopes()
{
var command = DevelopMcpCommand.CreateCommand(
_mockLogger, _mockToolingService,
graphApiService: _mockGraphApiService,
agentBlueprintService: _mockBlueprintService);

command.Subcommands.Select(sc => sc.Name).Should().Contain(
"add-byo-scopes",
because: "both blueprint and graph services are provided");
}
Copilot AI review requested due to automatic review settings July 8, 2026 21:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Comment on lines +8 to +10
/// <summary>
/// Response model for the GET /agents/mcpServers/byName/{serverName}/appIds endpoint.
/// </summary>
Comment on lines +97 to +111
private static async Task<string?> ResolveTenantIdAsync(CancellationToken cancellationToken)
{
// Use az account show to detect tenant ID from the current Azure CLI session
try
{
var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows);
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = isWindows ? "cmd.exe" : "az",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
Comment on lines +381 to +406
private static async Task<string?> ResolveTenantIdAsync(CancellationToken cancellationToken)
{
try
{
var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows);
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = isWindows ? "cmd.exe" : "az",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
if (isWindows)
{
startInfo.ArgumentList.Add("/c");
startInfo.ArgumentList.Add("az");
}
startInfo.ArgumentList.Add("account");
startInfo.ArgumentList.Add("show");
startInfo.ArgumentList.Add("--query");
startInfo.ArgumentList.Add("tenantId");
startInfo.ArgumentList.Add("-o");
startInfo.ArgumentList.Add("tsv");

Comment on lines +291 to +304
var graphToken = await _graphApiService.GetGraphAccessTokenAsync(tenantId, ct: cancellationToken);
if (string.IsNullOrWhiteSpace(graphToken))
{
_logger.LogError("Failed to acquire Graph API access token");
return GrantResult.Failed;
}

using var httpClient = Services.Internal.HttpClientFactory.CreateAuthenticatedClient(graphToken);

// Check existing grants
var filter = $"clientId eq '{clientSpId}' and resourceId eq '{resourceSpId}' and consentType eq '{AllPrincipalsConsentType}'";
var getUrl = $"{GraphApiConstants.BaseUrl}/v1.0/oauth2PermissionGrants?$filter={Uri.EscapeDataString(filter)}";

using var getResponse = await httpClient.GetAsync(getUrl, cancellationToken);
Comment on lines +674 to +686
command.SetHandler(async (context) =>
{
var blueprintId = context.ParseResult.GetValueForOption(blueprintIdOption)!;
var tenantId = context.ParseResult.GetValueForOption(tenantIdOption);
var ct = context.GetCancellationToken();

var executor = new GetAgentInstancesExecutor(logger, agentBlueprintService);
var success = await executor.ExecuteAsync(blueprintId, tenantId, ct);
if (!success)
{
context.ExitCode = 1;
}
});
Comment on lines +732 to +747
command.SetHandler(async (context) =>
{
var serverNames = context.ParseResult.GetValueForOption(serverNamesOption)!;
var blueprintId = context.ParseResult.GetValueForOption(blueprintIdOption);
var agentInstances = context.ParseResult.GetValueForOption(agentInstancesOption);
var tenantId = context.ParseResult.GetValueForOption(tenantIdOption);
var dryRun = context.ParseResult.GetValueForOption(dryRunOption);
var ct = context.GetCancellationToken();

var executor = new AddByoScopesExecutor(logger, toolingService, agentBlueprintService, graphApiService);
var success = await executor.ExecuteAsync(serverNames, blueprintId, agentInstances, tenantId, dryRun, ct);
if (!success)
{
context.ExitCode = 1;
}
});
Align with the slimmed-down MCP Platform endpoint that returns a
single-property JSON object { mcpServerAppId: ... }.

Renames:
- McpServerAppIdsResponse -> McpServerAppIdResponse
- GetMcpServerAppIdsByNameAsync -> GetMcpServerAppIdByNameAsync

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 22:02

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Comment on lines +100 to +109
/// <summary>
/// Retrieves the PPMI application (client) ID for a BYO MCP server by name.
/// Calls GET /agents/mcpServers/appIds?serverName={serverName} on the MCP Platform.
/// </summary>
/// <param name="serverName">MCP server name</param>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Response containing the MCP server's PPMI app ID, or null on failure</returns>
Task<McpServerAppIdResponse?> GetMcpServerAppIdByNameAsync(
string serverName,
CancellationToken cancellationToken = default);
Comment on lines +335 to +339
using var patchResponse = await httpClient.PatchAsync(
patchUrl,
new StringContent(JsonSerializer.Serialize(patchBody), Encoding.UTF8, "application/json"),
cancellationToken);

Comment on lines +360 to +363
using var createResponse = await httpClient.PostAsync(
createUrl,
new StringContent(JsonSerializer.Serialize(createBody), Encoding.UTF8, "application/json"),
cancellationToken);
Comment on lines +137 to +141
var mcpServerAppId = appIdResponse.McpServerAppId;
_logger.LogDebug("MCP server '{ServerName}' PPMI app ID: {AppId}", serverName, mcpServerAppId);

// Step 2: Resolve the PPMI app's service principal in the tenant
var ppmiSpId = await ResolveServicePrincipalIdAsync(tenantId, mcpServerAppId, cancellationToken);
Comment on lines +381 to +406
private static async Task<string?> ResolveTenantIdAsync(CancellationToken cancellationToken)
{
try
{
var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows);
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = isWindows ? "cmd.exe" : "az",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
if (isWindows)
{
startInfo.ArgumentList.Add("/c");
startInfo.ArgumentList.Add("az");
}
startInfo.ArgumentList.Add("account");
startInfo.ArgumentList.Add("show");
startInfo.ArgumentList.Add("--query");
startInfo.ArgumentList.Add("tenantId");
startInfo.ArgumentList.Add("-o");
startInfo.ArgumentList.Add("tsv");

Comment on lines +97 to +123
private static async Task<string?> ResolveTenantIdAsync(CancellationToken cancellationToken)
{
// Use az account show to detect tenant ID from the current Azure CLI session
try
{
var isWindows = System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(
System.Runtime.InteropServices.OSPlatform.Windows);
var startInfo = new System.Diagnostics.ProcessStartInfo
{
FileName = isWindows ? "cmd.exe" : "az",
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
if (isWindows)
{
startInfo.ArgumentList.Add("/c");
startInfo.ArgumentList.Add("az");
}
startInfo.ArgumentList.Add("account");
startInfo.ArgumentList.Add("show");
startInfo.ArgumentList.Add("--query");
startInfo.ArgumentList.Add("tenantId");
startInfo.ArgumentList.Add("-o");
startInfo.ArgumentList.Add("tsv");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants