{"record":{"id":"bf80efcef0e3388a","repo":"microsoft/aspire","slug":"mcp-tool-overlap-cannot-both-always-and-never-require","errorCode":null,"errorMessage":"MCP tool '{overlap}' cannot both always and never require approval.","messagePattern":"MCP tool '(.+?)' cannot both always and never require approval\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxToolDefinition.cs","lineNumber":254,"sourceCode":"                nameof(policy));\n        }\n\n        if (policy.Global is not null &&\n            policy.Global is not FoundryToolboxMcpGlobalApprovalMode.Never &&\n            policy.Global is not FoundryToolboxMcpGlobalApprovalMode.Always)\n        {\n            throw new ArgumentOutOfRangeException(\n                nameof(policy),\n                policy.Global,\n                \"The global MCP approval mode is not supported.\");\n        }\n\n        var overlap = always?.ToolNames\n            .Intersect(never?.ToolNames ?? [], StringComparer.Ordinal)\n            .FirstOrDefault();\n        if (overlap is not null)\n        {\n            throw new ArgumentException(\n                $\"MCP tool '{overlap}' cannot both always and never require approval.\",\n                nameof(policy));\n        }\n\n        if (always?.ReadOnly is { } alwaysReadOnly && never?.ReadOnly == alwaysReadOnly)\n        {\n            throw new ArgumentException(\n                $\"MCP tools with read_only set to '{alwaysReadOnly.ToString().ToLowerInvariant()}' cannot both always and never require approval.\",\n                nameof(policy));\n        }\n\n        return new(policy.Global, always, never);\n    }\n\n    public void WriteTo(Utf8JsonWriter writer)\n    {\n        if (Global is { } global)\n        {","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxToolDefinition.cs#L236-L272","documentation":"A single tool name appearing in both the Always and Never approval filters is contradictory — Foundry could not decide whether that tool requires approval. Create detects the first ordinal-intersection name and throws an ArgumentException naming the policy parameter.","triggerScenarios":"Building Always.ToolNames and Never.ToolNames from overlapping sources (e.g. a default list plus an exception list) so a tool like 'delete_item' ends up in both arrays.","commonSituations":"Merging filter lists from multiple config sources without deduplication by category; a rename causing the same tool to match two different filter rules; templated policy generation producing overlapping sets.","solutions":["Remove the conflicting tool name from one of the two lists.","Reclassify the tool into exactly one bucket (always or never).","Compute the lists programmatically so never = allTools - alwaysTools, guaranteeing disjoint sets.","Catch ArgumentException and surface the offending overlap name (it is in the message) to fix config."],"exampleFix":"// before\nAlways = new() { ToolNames = [\"delete\", \"write\"] },\nNever  = new() { ToolNames = [\"delete\"] }\n// after\nAlways = new() { ToolNames = [\"delete\", \"write\"] },\nNever  = new() { ToolNames = [\"read\"] }","handlingStrategy":"validation","validationCode":"var overlap = always.ToolNames.Intersect(never.ToolNames, StringComparer.Ordinal).FirstOrDefault();\nif (overlap is not null) throw new ArgumentException($\"'{overlap}' is in both always and never filters.\");","typeGuard":"static bool Disjoint(FoundryToolboxMcpApprovalFilter a, FoundryToolboxMcpApprovalFilter? n) =>\n    n is null || !a.ToolNames.Intersect(n.ToolNames ?? [], StringComparer.Ordinal).Any();","tryCatchPattern":"try { toolDefinition = CreateMcpTool(...); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"cannot both always and never require approval\"))\n{ logger.LogError(\"Remove the conflicting tool from one filter: {Message}\", ex.Message); }","preventionTips":["Derive never-lists as complements of always-lists to guarantee disjointness","Deduplicate tool lists loaded from multiple config sources","Test policies built from merged config before deploy"],"tags":["foundry","mcp","approval-policy","conflicting-configuration","aspire"],"backgroundTag":"conflicting-config-options","analyzedSha":"25830f84bd145686607ad00c057b3f84e2e51d43","analyzedAt":"2026-09-16T11:10:06.193Z","contentChangedAt":"2026-09-16T11:10:06.193Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}