microsoft/aspire · error · ArgumentException

An MCP approval policy must specify a global mode or at…

Error message

An MCP approval policy must specify a global mode or at least one custom filter.

What it means

An approval policy must say something: either a Global mode or at least one non-empty custom filter. If Global is null and both Always and Never filters are null (or resolve to nothing), ResolvedFoundryToolboxMcpApprovalPolicy.Create throws this ArgumentException because an empty policy has no representable wire form for require_approval.

Solutions

  1. Set Global = FoundryToolboxMcpGlobalApprovalMode.Never (or Always) on the policy.
  2. Add at least one tool name to Always.ToolNames or Never.ToolNames, or set ReadOnly on a filter.
  3. Pass null for ApprovalPolicy instead of an empty policy object if no approval behavior is desired.
  4. Log/inspect the policy object before constructing the tool definition.

Example fix

// before
new FoundryToolboxMcpApprovalPolicy { }
// after
new FoundryToolboxMcpApprovalPolicy { Global = FoundryToolboxMcpGlobalApprovalMode.Never }
Defensive patterns

Strategy: validation

Validate before calling

bool hasSomething = policy.Global is not null || policy.Always is not null || policy.Never is not null;
if (!hasSomething) throw new ArgumentException("Policy must specify a global mode or a filter.");

Type guard

static bool IsPopulated(FoundryToolboxMcpApprovalPolicy p) =>
    p.Global is not null || p.Always is not null || p.Never is not null;

Try / catch

try { toolDefinition = CreateMcpTool(...); }
catch (ArgumentException ex) when (ex.Message.Contains("must specify a global mode or at least one custom filter"))
{ logger.LogError("Approval policy is empty; set Global or add filters."); }

Prevention

When it happens

Trigger: new FoundryToolboxMcpApprovalPolicy() with every property left null; passing a default-initialized options object with ApprovalPolicy set but unpopulated; supplying filters whose ToolNames are empty and ReadOnly is null (those resolve to null filters, triggering this error).

Common situations: Declaring the policy object but forgetting to set any field; deserializing policy config from JSON where all keys were absent; conditionally adding filters that all ended up skipped.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of microsoft/aspire@25830f84bd (2026-09-16). Data as JSON: /api/errors/84f24cb94be92ddf. Report an issue: GitHub.

Appendix: source

Thrown at src/Aspire.Hosting.Foundry/Toolbox/FoundryToolboxToolDefinition.cs:234

        }

        var always = ResolvedFoundryToolboxMcpApprovalFilter.Create(
            policy.Always,
            nameof(policy.Always));
        var never = ResolvedFoundryToolboxMcpApprovalFilter.Create(
            policy.Never,
            nameof(policy.Never));

        if (policy.Global is not null && (always is not null || never is not null))
        {
            throw new ArgumentException(
                "A global MCP approval policy cannot be combined with custom filters.",
                nameof(policy));
        }

        if (policy.Global is null && always is null && never is null)
        {
            throw new ArgumentException(
                "An MCP approval policy must specify a global mode or at least one custom filter.",
                nameof(policy));
        }

        if (policy.Global is not null &&
            policy.Global is not FoundryToolboxMcpGlobalApprovalMode.Never &&
            policy.Global is not FoundryToolboxMcpGlobalApprovalMode.Always)
        {
            throw new ArgumentOutOfRangeException(
                nameof(policy),
                policy.Global,
                "The global MCP approval mode is not supported.");
        }

        var overlap = always?.ToolNames
            .Intersect(never?.ToolNames ?? [], StringComparer.Ordinal)
            .FirstOrDefault();
        if (overlap is not null)

View on GitHub (pinned to 25830f84bd)