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 @@ -23,6 +23,7 @@ Agents provisioned before this release need `Agent365.Observability.OtelWrite` g
**Option B — CLI** (`a365 setup admin`) has been removed in this release. Use Option A above, or copy the PowerShell instructions printed in the `a365 setup all` summary output.

### Added
- `--connectivity public|private` on `a365 develop-mcp register-external-mcp-server` — records whether the created Power Platform connector should bypass environment-level VNet injection. Defaults to `private`; `public` only takes effect in environments enabled for connector-level bypass (#498).
- Setup and bootstrap now use Microsoft's first-party Agent 365 CLI application when it is present in your tenant, validating it without changing Microsoft's app registration, and fall back to a tenant-owned "Agent 365 CLI" app when it is not (#489).
- Log separator written at the start of each CLI invocation now redacts values for secret-bearing options (e.g. `--idp-client-secret`) so they are not written to the log file in plain text.
- Authentication context (tenant and user) is now logged at the `Information` level whenever the resolved sign-in identity changes, giving operators a clear audit trail in the log file of who the CLI is acting as, without exposing credentials.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,9 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
var descriptionOption = new Option<string?>("--description", description: "Server description (required, used in MOS package metadata)");
command.AddOption(descriptionOption);

var connectivityOption = new Option<string?>("--connectivity", description: "Whether the remote MCP server is reachable publicly or only inside the environment's VNet: 'public' or 'private'. Defaults to 'private', which keeps environment-level VNet injection on the connector. 'public' asks Power Platform to bypass that injection; the bypass applies only to environments enabled for it, so verify connectivity afterwards.");
command.AddOption(connectivityOption);
Comment thread
lasrivas marked this conversation as resolved.

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

Expand Down Expand Up @@ -622,6 +625,7 @@ private static Command CreateRegisterExternalMcpServerSubcommand(
SecretLifetimeMonths: context.ParseResult.GetValueForOption(secretLifetimeMonthsOption),
PublisherName: context.ParseResult.GetValueForOption(publisherOption),
Description: context.ParseResult.GetValueForOption(descriptionOption),
Connectivity: context.ParseResult.GetValueForOption(connectivityOption),
DryRun: context.ParseResult.GetValueForOption(dryRunOption));

var executor = new RegisterCommandExecutor(logger, toolingService, graphApiService);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal record RawRegisterArgs(
int? SecretLifetimeMonths,
string? PublisherName,
string? Description,
string? Connectivity,
bool DryRun);

/// <summary>
Expand All @@ -57,7 +58,7 @@ internal RegisterCommandExecutor(
_retryHelper = retryHelper ?? new RetryHelper(logger, maxRetries: 5, baseDelaySeconds: 3);
}

private sealed record ResolvedInput
internal sealed record ResolvedInput
{
public required string ServerName { get; init; }
public required string ServerUrl { get; init; }
Expand All @@ -82,6 +83,7 @@ private sealed record ResolvedInput
public string? IdpClientSecret { get; init; }
public string? ApiKeyLocation { get; init; }
public string? ApiKeyName { get; init; }
public string? Connectivity { get; init; }
}

private sealed record EntraAppSet(
Expand Down Expand Up @@ -191,7 +193,7 @@ internal async Task<bool> ExecuteAsync(RawRegisterArgs args, CancellationToken c
return true;
}

private async Task<ResolvedInput?> ResolveInputsAsync(RawRegisterArgs args)
internal async Task<ResolvedInput?> ResolveInputsAsync(RawRegisterArgs args)
{
var serverName = args.ServerName;
var serverUrl = args.ServerUrl;
Expand All @@ -210,6 +212,7 @@ internal async Task<bool> ExecuteAsync(RawRegisterArgs args, CancellationToken c
var secretLifetimeMonths = args.SecretLifetimeMonths;
var publisherName = args.PublisherName;
var serverDescription = args.Description;
var connectivity = args.Connectivity;

RegisterExternalMcpServerInput? inputFileData = null;
if (!string.IsNullOrWhiteSpace(args.InputFile))
Expand Down Expand Up @@ -244,6 +247,7 @@ internal async Task<bool> ExecuteAsync(RawRegisterArgs args, CancellationToken c
secretLifetimeMonths ??= inputFileData.SecretLifetimeMonths;
publisherName ??= inputFileData.PublisherName;
serverDescription ??= inputFileData.Description;
connectivity ??= inputFileData.Connectivity;
Comment thread
lasrivas marked this conversation as resolved.

if (inputFileData.ExternalOAuth is not null)
{
Expand Down Expand Up @@ -311,6 +315,23 @@ internal async Task<bool> ExecuteAsync(RawRegisterArgs args, CancellationToken c
return null;
}

// Non-null rather than non-blank: `--connectivity " "` is a mistake, and treating it as
// absent would silently register the server as private, which is the opposite of what
// someone typing the option intends. It would also let a blank CLI value quietly
// override a valid input-file value through the ??= merge above.
if (connectivity is not null)
{
connectivity = connectivity.Trim();
if (!connectivity.Equals("public", StringComparison.OrdinalIgnoreCase)
&& !connectivity.Equals("private", StringComparison.OrdinalIgnoreCase))
{
_logger.LogError("--connectivity must be 'public' or 'private'. Got: '{Value}'", connectivity);
return null;
}

connectivity = connectivity.ToLowerInvariant();
}

if (string.IsNullOrWhiteSpace(authType))
{
authType = DevelopMcpCommand.InputValidator.PromptAndValidateRequiredInput("Enter authentication type (EntraOAuth, ExternalOAuth, APIKey, or NoAuth): ", "Auth type", 20);
Expand Down Expand Up @@ -507,6 +528,7 @@ internal async Task<bool> ExecuteAsync(RawRegisterArgs args, CancellationToken c
IdpClientSecret = idpClientSecret,
ApiKeyLocation = apiKeyLocation,
ApiKeyName = apiKeyName,
Connectivity = connectivity,
};
}

Expand All @@ -523,6 +545,12 @@ private void DisplayRegistrationSummary(ResolvedInput input)
DevelopMcpCommand.WriteLabel(" Auth Type: "); Console.WriteLine(input.AuthType);
DevelopMcpCommand.WriteLabel(" Publisher: "); Console.WriteLine(input.PublisherName);
DevelopMcpCommand.WriteLabel(" Description: "); Console.WriteLine(input.Description);
DevelopMcpCommand.WriteLabel(" Connectivity: "); Console.WriteLine(input.Connectivity ?? "private (default)");
if (string.Equals(input.Connectivity, "public", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine(" Note: the VNet bypass for 'public' applies only to environments enabled for it.");
Console.WriteLine(" Verify the server is reachable after registration.");
}
DevelopMcpCommand.WriteLabel(" Tools:");
Console.WriteLine();
foreach (var tool in input.ToolList)
Expand Down Expand Up @@ -685,6 +713,7 @@ private static AddMcpServerRequest BuildRequest(ResolvedInput input, EntraAppSet
RemoteServerScopes = input.RemoteScopes,
PublisherName = input.PublisherName,
Description = input.Description,
Connectivity = input.Connectivity,
Comment thread
lasrivas marked this conversation as resolved.
CopilotClientAppId = apps.PublicClientsClientId,
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ public class AddMcpServerRequest
/// </summary>
[JsonPropertyName("force")]
public bool Force { get; set; }

/// <summary>
/// Connectivity of the remote MCP server: "public" or "private". Null means private.
/// </summary>
[JsonPropertyName("connectivity")]
public string? Connectivity { get; set; }
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ public class RegisterExternalMcpServerInput
[JsonPropertyName("secretLifetimeMonths")]
public int? SecretLifetimeMonths { get; set; }

/// <summary>
/// Whether the remote MCP server is reachable publicly or only inside the environment's VNet:
/// "public" or "private". Defaults to "private" when omitted. Overridden by --connectivity.
/// </summary>
[JsonPropertyName("connectivity")]
public string? Connectivity { get; set; }

/// <summary>
/// External OAuth configuration (required when authType is ExternalOAuth)
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"tenantId": null,
"serviceTreeId": null,
"secretLifetimeMonths": null,
"connectivity": "private",
"force": false,
"externalOAuth": {
"authorizationUrl": null,
Expand Down
Loading
Loading