Skip to content

MEAI adapter: HostedMcpServerTool requests are always rejected #255

Description

@johanjeppson

Environment

  • Anthropic 12.44.0
  • Microsoft.Extensions.AI.Abstractions 10.9.0
  • .NET 10

Repro

Any HostedMcpServerTool passed through the beta AsIChatClient:

var client = new AnthropicClient(new ClientOptions { ApiKey = apiKey }).Beta.AsIChatClient();

var options = new ChatOptions
{
    ModelId = "claude-haiku-4-5",
    MaxOutputTokens = 1024,
    Tools = [new HostedMcpServerTool("microsoft_learn", "https://learn.microsoft.com/api/mcp")]
};

var response = await client.GetResponseAsync(
    [new ChatMessage(ChatRole.User, "What tools are available?")], options);

Actual

Anthropic.Exceptions.AnthropicBadRequestException: Status Code: BadRequest
{"type":"error","error":{"type":"invalid_request_error","message":"MCP server 'mcp' is defined but not referenced by any `mcp_toolset` in `tools`."}}

Note the server is reported as 'mcp' even though it was configured as microsoft_learn.

Cause

Three issues in the HostedMcpServerTool branch of the beta tool mapping
(AnthropicBetaClientExtensions):

  1. Wrong name property. The server definition is built with Name = mcp.Name — but
    HostedMcpServerTool.Name (the AITool.Name override) is hardcoded to the literal "mcp"
    in Microsoft.Extensions.AI 10.9; the configured server name is ServerName, which the
    mapping never reads. Every server serializes as "name": "mcp", so multiple servers also
    collide.
  2. No mcp_toolset is emitted. The mapping adds the server to mcp_servers and opts into
    the mcp-client-2025-11-20 beta, but never adds a BetaMcpToolset to tools. That API
    shape requires every mcp_servers entry to be referenced by exactly one MCPToolset
    (docs — validation rules),
    so the request is rejected unconditionally.
  3. Deprecated allowlist shape. AllowedTools is mapped to tool_configuration on the
    server definition, which is deprecated under mcp-client-2025-11-20 — the allowlist now
    belongs in the toolset's default_config/configs
    (migration guide).

The unit tests pin the broken output rather than catching it: GetResponseAsync_WithHostedMcpServerTool
asserts (against a verbatim mock) that the request contains "name": "mcp" with no toolset, and
GetResponseAsync_WithMultipleHostedMcpServerTools asserts both servers serialize as "name": "mcp" —
so CI passes while every live call fails.

Expected

case HostedMcpServerTool mcp:
    (betaHeaders ??= []).Add("mcp-client-2025-11-20");

    // The wire-level server name must be the configured ServerName — AITool.Name is the
    // fixed tool-type identifier "mcp" and would collide across multiple servers.
    (mcpServers ??= []).Add(new() { Name = mcp.ServerName, Url = mcp.ServerAddress });

    // mcp-client-2025-11-20 requires every mcp_servers entry to be referenced by exactly
    // one mcp_toolset in `tools`; tool_configuration on the server definition is deprecated.
    // AllowedTools maps to the documented allowlist pattern: disabled by default, listed
    // tools explicitly enabled.
    (createdTools ??= []).Add(
        mcp.AllowedTools is { Count: > 0 } allowedTools
            ? new BetaMcpToolset(mcp.ServerName)
            {
                DefaultConfig = new() { Enabled = false },
                Configs = allowedTools.ToDictionary(
                    static tool => tool,
                    static _ => new BetaMcpToolConfig { Enabled = true }),
            }
            : new BetaMcpToolset(mcp.ServerName)
    );
    break;

This makes HostedMcpServerTool usable end-to-end through AsIChatClient, matching the
AsAITool docs' guidance that the portable MEAI tool types are preferred over raw tool unions.

One migration note: callers currently working around this by passing HostedMcpServerTool
plus a hand-wrapped BetaMcpToolset (via AsAITool) for the same server would then send two
toolsets referencing one server, which the API also rejects — the fix may want to skip emitting
a toolset when one already exists for that ServerName, or call this out as a breaking note.

The test expectations in AnthropicClientBetaExtensionsTests need updating alongside, e.g.:

     "mcp_servers": [{
-        "name": "mcp",
+        "name": "my-mcp-server",
         "type": "url",
         "url": "https://mcp.example.com/server"
-    }]
+    }],
+    "tools": [{
+        "type": "mcp_toolset",
+        "mcp_server_name": "my-mcp-server"
+    }]

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions