Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Agents that export telemetry through the delegated (OBO) route need `Agent365.Ob
Blueprint agents that export telemetry through the app-only S2S endpoint don't need these permissions, and `a365 setup all` no longer requests them for blueprint agents (#501).

### Added
- `a365 develop-mcp list-published-servers` lists the MCP servers published to your tenant from every Dataverse environment you can access, with each server's approval status and source environment.
- `a365 develop-mcp grant-agents-access --agent-blueprint-id <GUID> --mcp-server-name <NAME>` reports which agent instances of a blueprint are missing the permission to call a BYO MCP server, and prompts you to select which ones to grant it to (#500).
- When more than one Entra application shares the MCP server's name, `a365 develop-mcp grant-agents-access` now lists them all and asks which one to use instead of failing (#500).
- `a365 develop-mcp grant-agents-access --help` now lists Microsoft's first-party agent blueprint names and IDs, and the same list is printed when `--agent-blueprint-id` is missing or not a GUID, so you can find the ID without looking it up elsewhere (#500).
Expand Down
1 change: 1 addition & 0 deletions docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ There is reference documentation for each command.
| [develop-mcp](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp) | Manage MCP servers in Dataverse environments. |
| [develop-mcp list-environments](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-list-environments) | List all Dataverse environments available for MCP server management. |
| [develop-mcp list-servers](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-list-servers) | List MCP servers in a specific Dataverse environment. |
| [develop-mcp list-published-servers](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-list-published-servers) | List MCP servers published to tenant scope from all accessible Dataverse environments. |
| [develop-mcp publish](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-publish) | Publish an MCP server to a Dataverse environment. |
| [develop-mcp unpublish](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-unpublish) | Unpublish an MCP server from a Dataverse environment. |
| [publish](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/publish) | Update manifest.json ID values and publish the package. Configure federated identity and app role assignments. |
Expand Down
4 changes: 4 additions & 0 deletions src/DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ The CLI provides a `develop-mcp` command for managing Model Context Protocol (MC

**Server Management:**
- `a365 develop-mcp list-servers -e <environment-id>` — List MCP servers in a specific Dataverse environment
- `a365 develop-mcp list-published-servers` — List MCP servers published to tenant scope from all accessible Dataverse environments
- `a365 develop-mcp publish -e <environment-id> -s <server-name>` — Publish an MCP server to a Dataverse environment
- `a365 develop-mcp unpublish -e <environment-id> -s <server-name>` — Unpublish an MCP server from a Dataverse environment

Expand All @@ -112,6 +113,9 @@ a365 develop-mcp list-environments
# List servers in a specific environment
a365 develop-mcp list-servers -e "Default-12345678-1234-1234-1234-123456789abc"

# List servers published to tenant scope from every environment you can access
a365 develop-mcp list-published-servers

# Publish a server with alias and display name
a365 develop-mcp publish \
--environment-id "Default-12345678-1234-1234-1234-123456789abc" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public static Command CreateCommand(
// Add subcommands
developMcpCommand.AddCommand(CreateListEnvironmentsSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreateListServersSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreateListPublishedServersSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreatePublishSubcommand(logger, toolingService, graphApiService));
developMcpCommand.AddCommand(CreateUnpublishSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreateRegisterExternalMcpServerSubcommand(logger, toolingService, graphApiService));
Expand Down Expand Up @@ -355,6 +356,69 @@ private static Command CreateListServersSubcommand(
return command;
}

/// <summary>
/// Creates the list-published-servers subcommand
/// </summary>
private static Command CreateListPublishedServersSubcommand(
ILogger logger,
IAgent365ToolingService toolingService)
{
var command = new Command("list-published-servers", "List MCP servers published to tenant scope from all accessible Dataverse environments");

var dryRunOption = new Option<bool>(
name: "--dry-run",
description: "Show what would be done without executing"
);
command.AddOption(dryRunOption);

// Verbose is handled globally in Program.cs (sets LogLevel.Debug); declared here so the parser accepts -v.
command.AddOption(new Option<bool>(["--verbose", "-v"], description: "Enable verbose logging"));

command.SetHandler(async (context) =>
{
logger.LogInformation("Starting list-published-servers operation...");

if (context.ParseResult.GetValueForOption(dryRunOption))
{
logger.LogInformation("[DRY RUN] Would read config from a365.config.json");
logger.LogInformation("[DRY RUN] Would query MCP servers published to tenant scope across all accessible Dataverse environments");
logger.LogInformation("[DRY RUN] Would display list of published MCP servers");
return;
}

var serversResponse = await toolingService.ListPublishedServersAsync(context.GetCancellationToken());

if (serversResponse == null)
{
logger.LogError("Failed to list published MCP servers");
context.ExitCode = 1;
return;
}

var servers = serversResponse.GetServers();

if (servers.Length == 0)
{
logger.LogInformation("No published MCP servers found");
return;
}

logger.LogInformation("Published MCP Servers:");
logger.LogInformation("======================");

foreach (var server in servers)
{
logger.LogInformation("{McpServerName}", server.McpServerName ?? "Unknown");
logger.LogInformation(" Status: {Status}", server.Status ?? "Unknown");
logger.LogInformation(" Source MCP Server Name: {SourceMcpServerName}", server.SourceMcpServerName ?? "Unknown");
logger.LogInformation(" Source Environment ID: {SourceEnvironmentId}", server.SourceEnvironmentId ?? "Unknown");
Comment on lines +411 to +414
}
logger.LogInformation("Listed {Count} published MCP server(s)", servers.Length);
});

return command;
}

/// <summary>
/// Creates the publish subcommand
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Text.Json.Serialization;

namespace Microsoft.Agents.A365.DevTools.Cli.Models;

/// <summary>
/// Model representing an MCP server published to tenant scope from a Dataverse environment
/// </summary>
public class PublishedMcpServer
{
/// <summary>
/// The tenant-level server name (alias)
/// </summary>
[JsonPropertyName("mcpServerName")]
public string? McpServerName { get; set; }

/// <summary>
/// The approval status (PendingApproval, Approved, or Blocked)
/// </summary>
[JsonPropertyName("status")]
public string? Status { get; set; }

/// <summary>
/// The server name in the source Dataverse environment
/// </summary>
[JsonPropertyName("sourceMcpServerName")]
public string? SourceMcpServerName { get; set; }

/// <summary>
/// The ID of the Dataverse environment the server was published from
/// </summary>
[JsonPropertyName("sourceEnvironmentId")]
public string? SourceEnvironmentId { get; set; }
}

/// <summary>
/// Response model for the list published MCP servers endpoint
/// </summary>
public class PublishedMcpServersResponse
{
/// <summary>
/// MCP servers published to tenant scope across all accessible Dataverse environments
/// </summary>
[JsonPropertyName("servers")]
public PublishedMcpServer[] Servers { get; set; } = Array.Empty<PublishedMcpServer>();

/// <summary>
/// Gets the servers array, or an empty array when the response carries none
/// </summary>
public PublishedMcpServer[] GetServers()
{
return Servers ?? Array.Empty<PublishedMcpServer>();
}
}
1 change: 1 addition & 0 deletions src/Microsoft.Agents.A365.DevTools.Cli/Models/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ This folder contains data models used throughout the CLI. Models are organized b
|-------|------|-------------|
| **DataverseEnvironment** | `DataverseEnvironment.cs` | Dataverse environment information |
| **DataverseMcpServer** | `DataverseMcpServer.cs` | MCP server in Dataverse |
| **PublishedMcpServer** | `PublishedMcpServer.cs` | MCP server published to tenant scope from a Dataverse environment |
| **PublishMcpServerRequest** | `PublishMcpServerRequest.cs` | MCP server publish request |
| **PublishMcpServerResponse** | `PublishMcpServerResponse.cs` | MCP server publish response |
| **ToolingManifest** | `ToolingManifest.cs` | MCP tooling manifest structure |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@ private string BuildListMcpServersUrl(string environment, string environmentId)
return $"{baseUrl}/agents/dataverse/environments/{environmentId}/mcpServers";
}

/// <summary>
/// Builds URL for listing MCP servers published to tenant scope across all accessible Dataverse environments
/// </summary>
private string BuildListPublishedMcpServersUrl(string environment)
{
var baseUrl = BuildAgent365ToolsBaseUrl(environment);
return $"{baseUrl}/agents/dataverse/publishedMcpServers";
}

/// <summary>
/// Builds URL for publishing an MCP server to a Dataverse environment. Hits the platform's v2
/// publish endpoint, which performs the full elevation orchestration (PPMI provisioning and MOS
Expand Down Expand Up @@ -507,6 +516,52 @@ private string BuildProvisionIdentityUrl(string environment, string serverName)
}
}

/// <inheritdoc />
public async Task<PublishedMcpServersResponse?> ListPublishedServersAsync(CancellationToken cancellationToken = default)
{
try
{
var endpointUrl = BuildListPublishedMcpServersUrl(_environment);
var correlationId = Internal.HttpClientFactory.GenerateCorrelationId();

_logger.LogDebug("Listing published MCP servers (CorrelationId: {CorrelationId})", correlationId);
_logger.LogDebug("Environment: {Env}", _environment);
_logger.LogDebug("Endpoint URL: {Url}", endpointUrl);

var audience = ConfigConstants.GetAgent365ToolsResourceAppId(_environment);
_logger.LogDebug("Acquiring access token for audience: {Audience}", audience);

var loginHint = await AzCliHelper.ResolveLoginHintAsync();
var authToken = await _authService.GetAccessTokenAsync(
audience, userId: loginHint, ct: cancellationToken, authorityHost: _authorityHost);
if (string.IsNullOrWhiteSpace(authToken))
{
_logger.LogError("Failed to acquire authentication token");
return null;
}

using var httpClient = Internal.HttpClientFactory.CreateAuthenticatedClient(authToken, correlationId: correlationId);

LogRequest("GET", endpointUrl);

using var response = await httpClient.GetAsync(endpointUrl, cancellationToken);

var (isSuccess, responseContent) = await ValidateResponseAsync(response, "list published MCP servers", cancellationToken);
if (!isSuccess)
{
return null;
}

return JsonDeserializationHelper.DeserializeWithDoubleSerialization<PublishedMcpServersResponse>(
responseContent, _logger);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to list published MCP servers");
return null;
}
}

/// <inheritdoc />
public async Task<PublishMcpServerResponse?> PublishServerAsync(
string environmentId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public interface IAgent365ToolingService
string environmentId,
CancellationToken cancellationToken = default);

/// <summary>
/// Lists MCP servers published to tenant scope from every Dataverse environment the caller can access
/// </summary>
/// <param name="cancellationToken">Cancellation token</param>
/// <returns>Response containing list of published MCP servers</returns>
Task<PublishedMcpServersResponse?> ListPublishedServersAsync(CancellationToken cancellationToken = default);

/// <summary>
/// Publishes an MCP server to a Dataverse environment via the platform's v2 publish endpoint,
/// which performs the full elevation orchestration (PPMI provisioning and MOS upload).
Expand Down
Loading
Loading