feat: add server-level MCP auto-approval for wildcard tool servers

MCP servers configured with empty tools arrays (wildcard) now appear in the
auto-approval pill with a server-level toggle. When toggled, a server-level
approval key (mcp_server:<name>) is added to autoApprovedToolNames. The
sidecar matches this key against PermissionRequestMcp.ServerName to auto-
approve all tools from that server without needing individual tool names.

- Add buildMcpServerApprovalKey/isMcpServerApprovalKey helpers
- Create approval groups for all MCP servers including empty-tools ones
- Add serverApprovalKey to ApprovalToolGroup for server-level toggles
- Update sidecar RequiresToolCallApproval to check server-level keys
- Include server-level keys in listApprovalToolNames for pruning safety
- Add tests for both shared domain and sidecar approval matching

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-28 18:34:19 +01:00
co-authored by Copilot
parent 53a08e0ed4
commit 3937904548
6 changed files with 210 additions and 25 deletions
@@ -57,9 +57,10 @@ internal sealed class CopilotApprovalCoordinator
{
string? toolName = ResolveApprovalToolName(request, toolNamesByCallId);
string? autoApprovedToolName = ResolveAutoApprovedToolName(request);
string? mcpServerApprovalKey = ResolveMcpServerApprovalKey(request);
string? approvalCacheKey = ResolveApprovalCacheKey(toolName, autoApprovedToolName);
if (IsToolApprovedForRequest(command.RequestId, approvalCacheKey)
|| !RequiresToolCallApproval(command.Pattern.ApprovalPolicy, agent.Id, toolName, autoApprovedToolName))
|| !RequiresToolCallApproval(command.Pattern.ApprovalPolicy, agent.Id, toolName, autoApprovedToolName, mcpServerApprovalKey))
{
return CreateApprovalResult(PermissionRequestResultKind.Approved);
}
@@ -227,7 +228,8 @@ internal sealed class CopilotApprovalCoordinator
ApprovalPolicyDto? approvalPolicy,
string agentId,
string? toolName,
string? autoApprovedToolName = null)
string? autoApprovedToolName = null,
string? mcpServerApprovalKey = null)
{
if (approvalPolicy?.Rules is null || approvalPolicy.Rules.Count == 0)
{
@@ -245,7 +247,8 @@ internal sealed class CopilotApprovalCoordinator
return true;
}
return !MatchesAutoApprovedTool(autoApprovedToolNames, toolName, autoApprovedToolName);
return !MatchesAutoApprovedTool(autoApprovedToolNames, toolName, autoApprovedToolName)
&& !MatchesAutoApprovedToolName(autoApprovedToolNames, mcpServerApprovalKey);
}
internal static bool TryGetApprovalToolName(
@@ -327,6 +330,19 @@ internal sealed class CopilotApprovalCoordinator
return GetFallbackToolName(request);
}
private const string McpServerApprovalPrefix = "mcp_server:";
private static string? ResolveMcpServerApprovalKey(PermissionRequest request)
{
if (request is not PermissionRequestMcp mcp)
{
return null;
}
string? serverName = NormalizeOptionalString(mcp.ServerName);
return serverName is not null ? $"{McpServerApprovalPrefix}{serverName}" : null;
}
private static string? ResolveApprovalCacheKey(
string? toolName,
string? autoApprovedToolName)