{"record":{"id":"9ae36bdb1f03f107","repo":"microsoft/aspire","slug":"mcp-tools-with-read-only-set-to-alwaysreadonly-tostring","errorCode":null,"errorMessage":"MCP tools with read_only set to '{alwaysReadOnly.ToString().ToLowerInvariant()}' cannot both always and never require approval.","messagePattern":"MCP tools with read_only set to '(.+?)' cannot both always and never require approval\\.","errorType":"validation","errorClass":"ArgumentException","httpStatus":null,"severity":"error","filePath":"src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxToolDefinition.cs","lineNumber":261,"sourceCode":"            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        {\n            writer.WriteStringValue(global switch\n            {\n                FoundryToolboxMcpGlobalApprovalMode.Never => \"never\",\n                FoundryToolboxMcpGlobalApprovalMode.Always => \"always\",\n                _ => throw new InvalidOperationException($\"Unsupported MCP approval mode '{global}'.\")\n            });\n            return;","sourceCodeStart":243,"sourceCodeEnd":279,"githubUrl":"https://github.com/microsoft/aspire/blob/25830f84bd145686607ad00c057b3f84e2e51d43/src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxToolDefinition.cs#L243-L279","documentation":"The OpenAI MCP approval filters support a read_only predicate instead of explicit tool names. Setting the same read_only boolean (true in both, or false in both) on the Always and Never filters is contradictory and rejected with this ArgumentException during policy creation.","triggerScenarios":"always: { ReadOnly = true } and never: { ReadOnly = true } (or both false) in the same FoundryToolboxMcpApprovalPolicy — e.g. intending 'read-only ops need no approval' but duplicating the flag on both filters.","commonSituations":"Copy-pasting a filter and only renaming always↔never while keeping ReadOnly set; config-driven filters where the same read_only value was templated into both buckets.","solutions":["Keep ReadOnly on only one filter (usually Never for read-only tools).","Give the two filters different ReadOnly values (true vs false) if both are needed.","Prefer explicit ToolNames filters instead of the read_only predicate to avoid the overlap.","Validate policy JSON/config so read_only appears in at most one of always/never."],"exampleFix":"// before\nAlways = new() { ReadOnly = true },\nNever  = new() { ReadOnly = true }\n// after\nNever = new() { ReadOnly = true }","handlingStrategy":"validation","validationCode":"if (always.ReadOnly is { } ar && never?.ReadOnly == ar)\n    throw new ArgumentException(\"read_only cannot be set to the same value on both filters.\");","typeGuard":"static bool ReadOnlyNotDuplicated(FoundryToolboxMcpApprovalFilter? a, FoundryToolboxMcpApprovalFilter? n) =>\n    a?.ReadOnly is null || n?.ReadOnly != a.ReadOnly;","tryCatchPattern":"try { toolDefinition = CreateMcpTool(...); }\ncatch (ArgumentException ex) when (ex.Message.Contains(\"read_only\"))\n{ logger.LogError(\"Set read_only on only one of always/never filters.\"); }","preventionTips":["Set ReadOnly on at most one filter","Prefer explicit ToolNames over the read_only predicate when unsure","Keep always/never filter templates distinct when generating config"],"tags":["foundry","mcp","approval-policy","read-only","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"}