@@ -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
0 commit comments