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):
- 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.
- 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.
- 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"
+ }]
Environment
Anthropic12.44.0Microsoft.Extensions.AI.Abstractions10.9.0Repro
Any
HostedMcpServerToolpassed through the betaAsIChatClient:Actual
Note the server is reported as
'mcp'even though it was configured asmicrosoft_learn.Cause
Three issues in the
HostedMcpServerToolbranch of the beta tool mapping(
AnthropicBetaClientExtensions):Name = mcp.Name— butHostedMcpServerTool.Name(theAITool.Nameoverride) is hardcoded to the literal"mcp"in Microsoft.Extensions.AI 10.9; the configured server name is
ServerName, which themapping never reads. Every server serializes as
"name": "mcp", so multiple servers alsocollide.
mcp_toolsetis emitted. The mapping adds the server tomcp_serversand opts intothe
mcp-client-2025-11-20beta, but never adds aBetaMcpToolsettotools. That APIshape requires every
mcp_serversentry to be referenced by exactly one MCPToolset(docs — validation rules),
so the request is rejected unconditionally.
AllowedToolsis mapped totool_configurationon theserver definition, which is deprecated under
mcp-client-2025-11-20— the allowlist nowbelongs in the toolset's
default_config/configs(migration guide).
The unit tests pin the broken output rather than catching it:
GetResponseAsync_WithHostedMcpServerToolasserts (against a verbatim mock) that the request contains
"name": "mcp"with no toolset, andGetResponseAsync_WithMultipleHostedMcpServerToolsasserts both servers serialize as"name": "mcp"—so CI passes while every live call fails.
Expected
This makes
HostedMcpServerToolusable end-to-end throughAsIChatClient, matching theAsAITooldocs' guidance that the portable MEAI tool types are preferred over raw tool unions.One migration note: callers currently working around this by passing
HostedMcpServerToolplus a hand-wrapped
BetaMcpToolset(viaAsAITool) for the same server would then send twotoolsets 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
AnthropicClientBetaExtensionsTestsneed 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" + }]