Skip to content

[Useability Issue] Unintuitive Serialization of MCPToolResource.RequireApproval Leads to Runtime Errors #52213

@jzuras

Description

@jzuras

Package: Azure.AI.Agents.Persistent (1.2.0-beta.2)
Namespace: Azure.AI.Agents.Persistent

Description

(Note - This is a summary of a discussion I had with Gemini while attempting to use the MCPToolResource.RequireApproval property.)

The design of the MCPToolResource.RequireApproval property, while flexible in accepting a string or a complex object, creates a significant usability issue that leads developers to a "pit of failure".

Because the property is typed as BinaryData, the developer is forced to manually serialize the MCPApprovalPerTool object. The most intuitive way to do this, using BinaryData.FromObjectAsJson(), invokes the default System.Text.Json.JsonSerializer. This serializer preserves C# PascalCase property names (Never, ToolNames), which do not match the camelCase and snake_case names expected by the API.

This mismatch inevitably leads to 400 Bad Request errors that are difficult for developers to diagnose and require a non-obvious, undiscoverable pattern to fix.

The Intuitive Path (Which Fails)

A developer's first instinct will be to construct the model and serialize it directly, as suggested by the RequireApproval property's own documentation.

// 1. Create the strongly-typed SDK model.
var approvalConfig = new MCPApprovalPerTool()
{
    Never = new MCPToolList(new List<string>() { "list_csv_files" })
};

var mcpToolResource = new MCPToolResource("my-server-label");

// 2. Serialize it using the standard, intuitive method with no options.
// This preserves the C# property names "Never" and "ToolNames".
mcpToolResource.RequireApproval = BinaryData.FromObjectAsJson(approvalConfig);

// 3. This will fail when used in a client.Runs.CreateRun() call.
var toolResources = mcpToolResource.ToToolResources(); 

Resulting Error

This intuitive approach produces an incorrect JSON payload, causing the API to return a 400 Bad Request error on the very first mismatched property.

{
  "error": {
    "message": "Unknown parameter: 'tool_resources.mcp[0].require_approval.Never'. Did you mean 'never'?",
    "type": "invalid_request_error",
    "param": "tool_resources.mcp[0].require_approval.Never",
    "code": "unknown_parameter"
  }
}

Notably, even if a developer attempts to solve this first error by applying a standard JsonNamingPolicy.CamelCase, they will immediately encounter a second error on the nested ToolNames property, which the API expects as tool_names (snake_case). This demonstrates that the problem cannot be solved with standard serialization techniques and requires a special approach.

The Correct (but Undiscoverable) Solution

The correct way to serialize the object is to bypass System.Text.Json entirely and explicitly invoke the SDK's internal serialization engine by casting the model to IPersistableModel<T> and using its Write method.

using System.ClientModel.Primitives; // Required for IPersistableModel and ModelReaderWriterOptions

// 1. Create the strongly-typed SDK model.
var approvalConfig = new MCPApprovalPerTool()
{
    Never = new MCPToolList(new List<string>() { "list_csv_files" })
};

var mcpToolResource = new MCPToolResource("my-server-label");

// 2. Use the non-obvious but correct serialization pattern.
// This leverages the SDK's internal serializer which correctly handles all property names.
BinaryData correctlySerializedData = ((IPersistableModel<MCPApprovalPerTool>)approvalConfig)
    .Write(new ModelReaderWriterOptions("W")); // "W" for wire format

// 3. Assign the correctly serialized data. This now works.
mcpToolResource.RequireApproval = correctlySerializedData;

var toolResources = mcpToolResource.ToToolResources();

Why This Should Be Considered a Usability Bug

While a working path exists, the current design is problematic for a preview SDK that needs to be approachable:

  1. It Fails the Principle of Least Astonishment: The most obvious code path leads directly to a runtime error.
  2. The Solution is Undiscoverable: There is no documentation, IntelliSense, or guidance that would lead a developer to the IPersistableModel.Write pattern. It requires inspecting the SDK's source code and understanding its internal architecture.
  3. It Creates a Poor Developer Experience: This issue can cause hours of frustrating debugging for what should be a straightforward task. This is precisely the kind of feedback that "Preview" releases are meant to solicit.

Actionable Suggestions for Improvement

To improve the developer experience, please consider one of the following changes:

  1. (Best) Add a Strongly-Typed Helper Method: Introduce a method on MCPToolResource that handles the complex serialization internally. This provides a clean, type-safe API and hides the implementation details.

    // Example of a better API
    public void SetApprovalConfiguration(MCPApprovalPerTool configuration)
    {
        this.RequireApproval = ((IPersistableModel<MCPApprovalPerTool>)configuration)
            .Write(new ModelReaderWriterOptions("W"));
    }
  2. (Good) Update XML Documentation: At a minimum, please update the XML documentation for the RequireApproval property with a code example demonstrating the correct IPersistableModel.Write pattern. This would make the solution discoverable.

Environment

VS 2022 and .Net v9

Metadata

Metadata

Assignees

Labels

AI AgentsClientThis issue is related to a non-management packageService AttentionWorkflow: This issue is responsible by Azure service team.customer-reportedIssues that are reported by GitHub users external to the Azure organization.issue-addressedWorkflow: The Azure SDK team believes it to be addressed and ready to close.questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions