Skip to content
Merged
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,14 @@ Agents provisioned before this release need `Agent365.Observability.OtelWrite` g
- `setup all`: fallback consent prompt now explains the ambiguity (declined vs error) and offers to grant the permissions programmatically via `az login`; per-audience permission lists and consent URLs show per-server MCP names (e.g. `mcp_MailTools`) instead of a generic `Agent 365 Tools` for every audience.

### Removed
- `a365 develop-mcp approve` and `a365 develop-mcp block` commands — MCP server approval and blocking are now managed through the Microsoft Admin Center rather than the CLI.
Comment thread
deepaligargms marked this conversation as resolved.
- `a365 develop-mcp package-mcp-server` command — MCP server package generation for Microsoft Admin Center submission is no longer produced by the CLI. The underlying `PackageMCPServerHelper` and `IAgent365ToolingService.GetServerInfoAsync`/`ApproveServerAsync`/`BlockServerAsync` service methods were removed with it.
- `a365 config` command family (`config init`, `config display`, `config permissions`) — replaced by `a365 setup all --agent-name` and `a365 setup permissions custom`.

### Breaking Changes
- **`a365 develop-mcp approve` removed** — server approval is now performed by tenant admins via the Microsoft Admin Center.
- **`a365 develop-mcp block` removed** — server blocking is now performed by tenant admins via the Microsoft Admin Center.
- **`a365 develop-mcp package-mcp-server` removed** — MCP server packages for Microsoft Admin Center submission are no longer produced by the CLI; admins onboard and manage servers directly through the Microsoft Admin Center.
- **`a365 config init` removed** — replace with `a365 setup all --agent-name <name>`. This creates the agent blueprint, configures permissions, and registers the messaging endpoint in one step without requiring a pre-existing config file.
- **`a365 config display` removed** — use `a365 query-entra blueprint-scopes` to inspect live blueprint permissions and consent state.
- **`a365 config permissions` removed** — replace with `a365 setup permissions custom --resource-app-id <guid> --scopes <scopes>`.
Expand Down
3 changes: 0 additions & 3 deletions docs/commands/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,6 @@ There is reference documentation for each command.
| [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 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. |
| [develop-mcp approve](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-approve) | Approve an MCP server. |
| [develop-mcp block](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-block) | Block an MCP server. |
Comment thread
deepaligargms marked this conversation as resolved.
| [develop-mcp package-mcp-server](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/develop-mcp#develop-mcp-package-mcp-server) | Generate MCP server package for submission on Microsoft admin center. |
| [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. |
| [query-entra](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/query-entra) | Query Microsoft Entra ID for agent information including scopes, permissions, and consent status. |
| [query-entra blueprint-scopes](https://learn.microsoft.com/microsoft-agent-365/developer/reference/cli/query-entra#query-entra-blueprint-scopes) | List configured scopes and consent status for the agent blueprint. |
Expand Down
7 changes: 0 additions & 7 deletions src/DEVELOPER.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,6 @@ The CLI provides a `develop-mcp` command for managing Model Context Protocol (MC
- `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

**Server Approval (Global Operations):**
- `a365 develop-mcp approve -s <server-name>` — Approve an MCP server
- `a365 develop-mcp block -s <server-name>` — Block an MCP server

**Key Features:**
- **Azure CLI Style Parameters:** Uses named options (`--environment-id/-e`, `--server-name/-s`) for better UX
- **Dry Run Support:** All commands support `--dry-run` for safe testing
Expand Down Expand Up @@ -126,9 +122,6 @@ a365 develop-mcp publish \
# Quick unpublish with short aliases
a365 develop-mcp unpublish -e "Default-12345678-1234-1234-1234-123456789abc" -s "msdyn_MyMcpServer"

# Approve a server (global operation)
a365 develop-mcp approve --server-name "msdyn_MyMcpServer"

# Test commands safely with dry-run
a365 develop-mcp publish -e "myenv" -s "myserver" --dry-run

Expand Down
228 changes: 0 additions & 228 deletions src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopMcpCommand.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.Agents.A365.DevTools.Cli.Helpers;
using Microsoft.Agents.A365.DevTools.Cli.Models;
using Microsoft.Agents.A365.DevTools.Cli.Services;
Comment thread
deepaligargms marked this conversation as resolved.
using Microsoft.Extensions.Logging;
using System.CommandLine;
using static Microsoft.Agents.A365.DevTools.Cli.Helpers.PackageMCPServerHelper;

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

Expand Down Expand Up @@ -37,9 +35,6 @@ public static Command CreateCommand(
developMcpCommand.AddCommand(CreateListServersSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreatePublishSubcommand(logger, toolingService, graphApiService));
developMcpCommand.AddCommand(CreateUnpublishSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreateApproveSubcommand(logger, toolingService));
Comment thread
deepaligargms marked this conversation as resolved.
developMcpCommand.AddCommand(CreateBlockSubcommand(logger, toolingService));
developMcpCommand.AddCommand(CreatePackageMCPServerSubCommand(logger, toolingService));
developMcpCommand.AddCommand(CreateRegisterExternalMcpServerSubcommand(logger, toolingService, graphApiService));

return developMcpCommand;
Expand Down Expand Up @@ -458,229 +453,6 @@ private static Command CreateUnpublishSubcommand(
return command;
}

/// <summary>
/// Creates the approve subcommand
/// </summary>
private static Command CreateApproveSubcommand(ILogger logger, IAgent365ToolingService toolingService)
{
var command = new Command("approve", "Approve an MCP server");

var serverNameOption = new Option<string?>(
["--server-name", "-s"],
description: "MCP server name to approve"
);
serverNameOption.IsRequired = false; // Allow null so we can prompt
command.AddOption(serverNameOption);

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

var verboseOption = new Option<bool>(
["--verbose", "-v"],
description: "Enable verbose logging"
);
command.AddOption(verboseOption);

command.SetHandler(async (serverName, dryRun, verbose) =>
{
_ = verbose;
try
{
// Validate and prompt for missing required arguments with security checks
if (string.IsNullOrWhiteSpace(serverName))
{
serverName = InputValidator.PromptAndValidateRequiredInput("Enter MCP server name to approve: ", "Server name", 100);
if (string.IsNullOrWhiteSpace(serverName))
{
logger.LogError("Server name is required");
return;
}
}
else
{
// Validate provided server name
serverName = InputValidator.ValidateInput(serverName, "Server name");
if (serverName == null)
{
logger.LogError("Invalid server name format");
return;
}
}
}
catch (ArgumentException ex)
{
logger.LogError("Input validation failed: {Message}", ex.Message);
return;
}

logger.LogInformation("Starting approve operation for server {ServerName}...", serverName);

if (dryRun)
{
logger.LogInformation("[DRY RUN] Would read config from a365.config.json");
logger.LogInformation("[DRY RUN] Would approve MCP server {ServerName}", serverName);
await Task.CompletedTask;
return;
}

// Call service
var success = await toolingService.ApproveServerAsync(serverName);

if (!success)
{
logger.LogError("Failed to approve MCP server {ServerName}", serverName);
return;
}

logger.LogInformation("Successfully approved MCP server {ServerName}", serverName);

}, serverNameOption, dryRunOption, verboseOption);

return command;
}

/// <summary>
/// Creates the block subcommand
/// </summary>
private static Command CreateBlockSubcommand(ILogger logger, IAgent365ToolingService toolingService)
{
var command = new Command("block", "Block an MCP server");

var serverNameOption = new Option<string?>(
["--server-name", "-s"],
description: "MCP server name to block"
);
serverNameOption.IsRequired = false; // Allow null so we can prompt
command.AddOption(serverNameOption);

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

var verboseOption = new Option<bool>(
["--verbose", "-v"],
description: "Enable verbose logging"
);
command.AddOption(verboseOption);

command.SetHandler(async (serverName, dryRun, verbose) =>
{
_ = verbose;
try
{
// Validate and prompt for missing required arguments with security checks
if (string.IsNullOrWhiteSpace(serverName))
{
serverName = InputValidator.PromptAndValidateRequiredInput("Enter MCP server name to block: ", "Server name", 100);
if (string.IsNullOrWhiteSpace(serverName))
{
logger.LogError("Server name is required");
return;
}
}
else
{
// Validate provided server name
serverName = InputValidator.ValidateInput(serverName, "Server name");
if (serverName == null)
{
logger.LogError("Invalid server name format");
return;
}
}
}
catch (ArgumentException ex)
{
logger.LogError("Input validation failed: {Message}", ex.Message);
return;
}

logger.LogInformation("Starting block operation for server {ServerName}...", serverName);

if (dryRun)
{
logger.LogInformation("[DRY RUN] Would read config from a365.config.json");
logger.LogInformation("[DRY RUN] Would block MCP server {ServerName}", serverName);
await Task.CompletedTask;
return;
}

// Call service
var success = await toolingService.BlockServerAsync(serverName);

if (!success)
{
logger.LogError("Failed to block MCP server {ServerName}", serverName);
return;
}

logger.LogInformation("Successfully blocked MCP server {ServerName}", serverName);

}, serverNameOption, dryRunOption, verboseOption);

return command;
}

/// <summary>
/// Creates the package generation subcommand
/// </summary>
private static Command CreatePackageMCPServerSubCommand(ILogger logger, IAgent365ToolingService toolingService)
{
var command = new Command("package-mcp-server", "Generate MCP server package for submission on Microsoft admin center");

var serverNameOption = new Option<string>("--server-name", "MCP server name") { IsRequired = true };
var developerNameOption = new Option<string>("--developer-name", "Publisher/developer display name") { IsRequired = true };
var iconUrlOption = new Option<string>("--icon-url", "Public URL to a PNG icon for the MCP server") { IsRequired = true };
var outputPathOption = new Option<string>("--output-path", "Target directory for the generated ZIP package") { IsRequired = true };
var dryRunOption = new Option<bool>(name: "--dry-run", description: "Show what would be done without executing");
var verboseOption = new Option<bool>(
["--verbose", "-v"],
description: "Enable verbose logging"
);

command.AddOption(serverNameOption);
command.AddOption(developerNameOption);
command.AddOption(iconUrlOption);
command.AddOption(outputPathOption);
command.AddOption(dryRunOption);
command.AddOption(verboseOption);

command.SetHandler(async (serverName, developerName, iconUrl, outputPath, dryRun, verbose) =>
{
_ = verbose;
if (dryRun)
{
logger.LogInformation("[DRY RUN] Would query MCP servers management endpoint to fetch details of the MCP server");
logger.LogInformation("[DRY RUN] Fetch the icon from the provided url");
logger.LogInformation("[DRY RUN] Build the package content and put it in the target directory");
await Task.CompletedTask;
return;
}

logger.LogInformation("Starting package creation...");

try
{
var serverInfo = await toolingService.GetServerInfoAsync(serverName);
var manifest = PackageMCPServerHelper.GenerateManifestJson(serverInfo, developerName, logger);
var zipFilePath = PackageMCPServerHelper.BuildPackage(manifest, serverInfo, iconUrl, outputPath);
logger.LogInformation("Package was created successfully at {zipFilePath}", zipFilePath);
}
catch (Exception ex)
{
logger.LogError(ex, "Package creation failed");
}

}, serverNameOption, developerNameOption, iconUrlOption, outputPathOption, dryRunOption, verboseOption);

return command;
}

/// <summary>
/// Creates the register-external-mcp-server subcommand
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,54 +215,4 @@ public static string[] GetAllScopes()
}
}

// PackageMCPServer constants
public static class PackageMCPServer
{
public const string OutlinePngIconFileName = "outline.png";
public const string ColorPngIconFileName = "color.png";
public const string ManifestFileName = "manifest.json";
public const string TemplateManifestJson =
@"
{
""$schema"": ""https://developer.microsoft.com/en-us/json-schemas/teams/vDevPreview/MicrosoftTeams.schema.json"",
""manifestVersion"": ""devPreview"",
""agentConnectors"": [
{
""id"": ""11111111-1111-1111-1111-111111111112"",
""displayName"": ""DUMMY_DISPLAY_NAME"",
""description"": ""DUMMY_DESCRIPTION"",
""toolSource"": {
""remoteMcpServer"": {
""mcpServerUrl"": ""https://example.com/mcpServer"",
""authorization"": {
""type"": ""None""
}
}
}
}
],
""version"": ""1.0.0"",
""id"": ""11111111-1111-1111-1111-111111111112"",
""developer"": {
""name"": ""DUMMY_DEVELOPER"",
""websiteUrl"": ""https://go.microsoft.com/fwlink/?linkid=2138949"",
""privacyUrl"": ""https://go.microsoft.com/fwlink/?linkid=2138865"",
""termsOfUseUrl"": ""https://go.microsoft.com/fwlink/?linkid=2138950""
},
""name"": {
""short"": ""DUMMY_SHORT_NAME"",
""full"": ""DUMMY_FULL_NAME""
},
""description"": {
""short"": ""DUMMY_SHORT_DESCRIPTION"",
""full"": ""DUMMY_FULL_DESCRIPTION""
},
""icons"": {
""outline"": ""outline.png"",
""color"": ""color.png""
},
""accentColor"": ""#E0F6FC""
}";
}

}
Loading
Loading