anthropics / anthropics/anthropic-sdk-csharp

MEAI adapter: HostedMcpServerTool requests are always rejected

Abierto
#255 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
C#
Estrellas
322
Forks
105
Merge medio
1 d 6 h
PR fusionados (30 d)
9

Descripción

## Environment

- `Anthropic` 12.44.0
- `Microsoft.Extensions.AI.Abstractions` 10.9.0
- .NET 10

## Repro

Any `HostedMcpServerTool` passed through the beta `AsIChatClient`:

```csharp
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](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#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](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector#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

```csharp
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.:

```diff
"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"
+ }]
```

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.