Skip to content

Commit f009a04

Browse files
bhaarathmsclaude
andcommitted
feat: add publisher, description, force flag, clean output, and error handling to register-external-mcp-server
- Added --publisher (mandatory) and --description (mandatory) options for MOS package metadata - Added --force option to delete/recreate Entra apps via Graph API lookup by display name - Added GetAppObjectIdByDisplayNameAsync and DeleteEntraAppAsync to GraphApiService - Downgraded service-level logs to LogDebug for clean non-verbose output - Removed spinner code, kept simple "Registering..." message - Server name max length set to 22 characters - Warning accumulation for non-fatal PPMI/redirect URI failures Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent a891067 commit f009a04

5 files changed

Lines changed: 216 additions & 123 deletions

File tree

src/Microsoft.Agents.A365.DevTools.Cli/Commands/DevelopMcpCommand.cs

Lines changed: 70 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -813,7 +813,7 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
813813
{
814814
var command = new Command("register-external-mcp-server", "Register an external MCP server with Entra, ExternalIDP, or NoAuth authentication");
815815

816-
var serverNameOption = new Option<string?>(["--server-name", "-s"], description: "MCP server name (max 27 chars). If no '<prefix>_' is present, 'ext_' is auto-prepended.");
816+
var serverNameOption = new Option<string?>(["--server-name", "-s"], description: "MCP server name (max 22 chars). If no '<prefix>_' is present, 'ext_' is auto-prepended.");
817817
command.AddOption(serverNameOption);
818818

819819
var serverUrlOption = new Option<string?>(["--server-url", "-u"], description: "Remote MCP server URL");
@@ -860,6 +860,15 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
860860
var configOption = new Option<string>(["-c", "--config"], getDefaultValue: () => "a365.config.json", description: "Configuration file path");
861861
command.AddOption(configOption);
862862

863+
var publisherOption = new Option<string?>("--publisher", description: "Publisher name (required, used in MOS package metadata)");
864+
command.AddOption(publisherOption);
865+
866+
var descriptionOption = new Option<string?>("--description", description: "Server description (required, used in MOS package metadata)");
867+
command.AddOption(descriptionOption);
868+
869+
var forceOption = new Option<bool>("--force", description: "Force re-creation of Entra apps and connectors");
870+
command.AddOption(forceOption);
871+
863872
var dryRunOption = new Option<bool>("--dry-run", description: "Show what would be done without executing");
864873
command.AddOption(dryRunOption);
865874

@@ -882,6 +891,9 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
882891
var remoteScopes = context.ParseResult.GetValueForOption(remoteScopesOption);
883892
var userTenantId = context.ParseResult.GetValueForOption(tenantIdOption);
884893
var serviceTreeId = context.ParseResult.GetValueForOption(serviceTreeIdOption);
894+
var publisherName = context.ParseResult.GetValueForOption(publisherOption);
895+
var serverDescription = context.ParseResult.GetValueForOption(descriptionOption);
896+
var force = context.ParseResult.GetValueForOption(forceOption);
885897
var configPath = context.ParseResult.GetValueForOption(configOption)!;
886898
var dryRun = context.ParseResult.GetValueForOption(dryRunOption);
887899
var verbose = context.ParseResult.GetValueForOption(verboseOption);
@@ -915,7 +927,7 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
915927
}
916928

917929
// Validate server name length (max 27 chars including prefix)
918-
const int maxServerNameLength = 27;
930+
const int maxServerNameLength = 22;
919931
if (serverName.Length > maxServerNameLength)
920932
{
921933
logger.LogError("Server name '{ServerName}' is {Length} characters, exceeding the maximum of {Max} characters (including prefix)", serverName, serverName.Length, maxServerNameLength);
@@ -1037,6 +1049,20 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
10371049
}
10381050
}
10391051

1052+
// Publisher name is required
1053+
if (string.IsNullOrWhiteSpace(publisherName))
1054+
{
1055+
publisherName = InputValidator.PromptAndValidateRequiredInput("Enter publisher name: ", "Publisher name", 200);
1056+
if (string.IsNullOrWhiteSpace(publisherName)) { logger.LogError("Publisher name is required"); return; }
1057+
}
1058+
1059+
// Server description is required
1060+
if (string.IsNullOrWhiteSpace(serverDescription))
1061+
{
1062+
serverDescription = InputValidator.PromptAndValidateRequiredInput("Enter server description: ", "Server description", 500);
1063+
if (string.IsNullOrWhiteSpace(serverDescription)) { logger.LogError("Server description is required"); return; }
1064+
}
1065+
10401066
// Remote scopes are optional — if empty, the Remote Proxy connector uses NoAuth
10411067
// Skip for NoAuth and APIKey since no scopes are needed
10421068
if (!isNoAuth && !isApiKey && string.IsNullOrWhiteSpace(remoteScopes))
@@ -1051,14 +1077,14 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
10511077
return;
10521078
}
10531079

1054-
logger.LogInformation("Registering MCP server '{ServerName}' (AuthType: {AuthType})...", serverName, authType);
1055-
10561080
if (dryRun)
10571081
{
10581082
logger.LogInformation("[DRY RUN] Would create Entra apps and register MCP server {ServerName}", serverName);
10591083
return;
10601084
}
10611085

1086+
Console.WriteLine($"Registering MCP server '{serverName}'...");
1087+
10621088
// Step 1: Create Entra app(s) and get secrets
10631089

10641090
// Auto-detect tenant ID from az account if not provided
@@ -1118,6 +1144,29 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
11181144
return;
11191145
}
11201146

1147+
// Force mode: delete existing Entra apps before recreating
1148+
if (force)
1149+
{
1150+
logger.LogDebug("Force mode: looking up existing Entra apps to delete...");
1151+
1152+
var existingA365ObjectId = await graphApiService.GetAppObjectIdByDisplayNameAsync(tenantId, $"{serverName}-A365Proxy");
1153+
if (!string.IsNullOrWhiteSpace(existingA365ObjectId))
1154+
{
1155+
logger.LogDebug("Deleting existing A365 Proxy app: {ObjectId}", existingA365ObjectId);
1156+
await graphApiService.DeleteEntraAppAsync(tenantId, existingA365ObjectId);
1157+
}
1158+
1159+
if (isEntra)
1160+
{
1161+
var existingRemoteObjectId = await graphApiService.GetAppObjectIdByDisplayNameAsync(tenantId, $"{serverName}-RemoteProxy");
1162+
if (!string.IsNullOrWhiteSpace(existingRemoteObjectId))
1163+
{
1164+
logger.LogDebug("Deleting existing Remote Proxy app: {ObjectId}", existingRemoteObjectId);
1165+
await graphApiService.DeleteEntraAppAsync(tenantId, existingRemoteObjectId);
1166+
}
1167+
}
1168+
}
1169+
11211170
logger.LogDebug("Creating Entra application for A365 Proxy...");
11221171
var a365App = await graphApiService.CreateEntraAppAsync(tenantId, $"{serverName}-A365Proxy", serviceTreeId: serviceTreeId);
11231172
if (a365App == null)
@@ -1214,6 +1263,9 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
12141263
Name = apiKeyName,
12151264
} : null,
12161265
RemoteServerScopes = remoteScopes,
1266+
PublisherName = publisherName,
1267+
Description = serverDescription,
1268+
Force = force,
12171269
};
12181270

12191271
Models.AddMcpServerResponse? addResponse;
@@ -1391,45 +1443,33 @@ await graphApiService.AddRequiredResourceAccessAsync(
13911443
}
13921444

13931445
// Step 5: Show completion summary
1394-
logger.LogInformation("");
13951446
if (warnings.Count == 0)
13961447
{
1397-
logger.LogInformation("MCP server '{ServerName}' has been registered successfully.", serverName);
1448+
var prevColor = Console.ForegroundColor;
1449+
Console.ForegroundColor = ConsoleColor.Green;
1450+
Console.WriteLine($"MCP server '{serverName}' has been registered successfully.");
1451+
Console.ForegroundColor = prevColor;
13981452
}
13991453
else
14001454
{
1401-
logger.LogInformation("MCP server '{ServerName}' was registered with {Count} warning(s):", serverName, warnings.Count);
1402-
logger.LogInformation("");
1455+
var prevColor = Console.ForegroundColor;
1456+
Console.ForegroundColor = ConsoleColor.Yellow;
1457+
Console.WriteLine($"MCP server '{serverName}' was registered with {warnings.Count} warning(s):");
1458+
Console.ForegroundColor = prevColor;
1459+
Console.WriteLine();
14031460
foreach (var w in warnings)
14041461
{
14051462
logger.LogWarning(" - {Warning}", w);
14061463
}
14071464
}
14081465

1409-
logger.LogInformation("");
1410-
var appNumber = 1;
1411-
logger.LogInformation("The following Entra applications were created:");
1412-
logger.LogInformation(" {Num}. A365 Proxy: {ClientId} ({DisplayName})", appNumber++, authMetadata!.ClientApp1Id, $"{serverName}-A365Proxy");
1413-
if (isEntra)
1414-
{
1415-
logger.LogInformation(" {Num}. Remote Proxy: {ClientId} ({DisplayName})", appNumber++, authMetadata.ClientApp2Id, $"{serverName}-RemoteProxy");
1416-
}
1417-
if (!string.IsNullOrWhiteSpace(ppmiAppClientId))
1418-
{
1419-
logger.LogInformation(" {Num}. PPMI App: {ClientId} ({DisplayName})", appNumber++, ppmiAppClientId, serverName);
1420-
}
1421-
logger.LogInformation("");
1422-
logger.LogInformation("Please ask your tenant admin to grant admin consent on all Entra applications listed above.");
1466+
Console.WriteLine();
1467+
Console.WriteLine($"Please ask your tenant admin to approve MCP server '{serverName}'.");
14231468
if (isExternalIdp && !string.IsNullOrWhiteSpace(remoteRedirectUri))
14241469
{
1425-
logger.LogInformation("");
1426-
logger.LogInformation("Redirect URI: {RedirectUri}", remoteRedirectUri);
1427-
logger.LogInformation("Please add this redirect URI to your external IDP application ({ClientId}).", idpClientId);
1428-
}
1429-
if (isApiKey)
1430-
{
1431-
logger.LogInformation("");
1432-
logger.LogInformation("API Key Authentication: {Location} ({Name})", apiKeyLocation, apiKeyName);
1470+
Console.WriteLine();
1471+
Console.WriteLine($"Redirect URI: {remoteRedirectUri}");
1472+
Console.WriteLine($"Please add this redirect URI to your external IDP application ({idpClientId}).");
14331473
}
14341474
});
14351475

src/Microsoft.Agents.A365.DevTools.Cli/Models/AddMcpServerRequest.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,24 @@ public class AddMcpServerRequest
6262
/// </summary>
6363
[JsonPropertyName("apiKeyDetails")]
6464
public ApiKeyDetails? ApiKeyDetails { get; set; }
65+
66+
/// <summary>
67+
/// Publisher name for MOS package metadata
68+
/// </summary>
69+
[JsonPropertyName("publisherName")]
70+
public string? PublisherName { get; set; }
71+
72+
/// <summary>
73+
/// Server description for MOS package metadata
74+
/// </summary>
75+
[JsonPropertyName("description")]
76+
public string? Description { get; set; }
77+
78+
/// <summary>
79+
/// When true, force re-creation of connectors and reuse existing MCC record
80+
/// </summary>
81+
[JsonPropertyName("force")]
82+
public bool Force { get; set; }
6583
}
6684

6785
/// <summary>

0 commit comments

Comments
 (0)