Skip to content

.NET: [Bug]: Hosted MCP calls remain sequential despite parallel_tool_calls=true #7825

Description

Description

What happened?

When a .NET Agent Framework agent uses a provider-hosted HostedMcpServerTool, independent MCP calls are executed sequentially even when both model-side multiple tool calls and framework-side concurrent invocation are enabled.

The setup uses the public Microsoft Learn MCP server (https://learn.microsoft.com/api/mcp) and the microsoft_docs_search tool. The prompt explicitly requests exactly two independent searches in one tool-call batch before waiting for either result.

The response contains two distinct MCP calls, so the parallel/multiple-tool-call configuration is accepted, but the second call does not enter in_progress until the first call has completed. This was reproduced in three consecutive runs.

Expected behavior

Both independent MCP calls should start before either call completes. AllowMultipleToolCalls = true should allow the model to emit both calls in one response, and AllowConcurrentInvocation = true should allow them to execute concurrently where supported.

Actual behavior

Two MCP calls are generated, but execution is serialized. The second call starts 23 ms, 5 ms, and 9 ms after the first call completes across three runs.

Steps to reproduce

  1. Start from dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools.
  2. Configure only the provider-hosted Microsoft Learn MCP tool (HostedMcpServerTool) with approval disabled.
  3. Create the agent with ChatOptions.AllowMultipleToolCalls = true and ChatClientAgentOptions.AllowConcurrentInvocation = true.
  4. Use gpt-5.4-mini and send this prompt:

You must perform exactly two independent Microsoft Learn searches. Issue both searches together in the same tool-call batch before waiting for either result. Do not use the result of one search to formulate the other. Search 1: Azure Functions Flex Consumption scaling. Search 2: Azure Container Apps Jobs scaling. After both complete, summarize each in one bullet.

  1. Capture MCP lifecycle events and compare the in_progress and completed timestamps for each call.

Question

Is concurrent execution expected for provider-hosted MCP calls, or do AllowConcurrentInvocation and AllowMultipleToolCalls only apply to locally invoked AIFunction/MCP tools? If hosted MCP execution is intentionally serialized, the API documentation should call out that distinction. Otherwise, this appears to be a bug in the Hosted MCP / Responses orchestration path.

Code Sample

#pragma warning disable MEAI001

using Azure.AI.Projects;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Foundry.Hosting;
using Microsoft.Extensions.AI;

var endpoint = new Uri(Environment.GetEnvironmentVariable("FOUNDRY_PROJECT_ENDPOINT")!);
var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME")
    ?? "gpt-5.4-mini";

AITool learnMcp = new HostedMcpServerTool(
    serverName: "microsoft_learn",
    serverAddress: "https://learn.microsoft.com/api/mcp")
{
    AllowedTools = ["microsoft_docs_search"],
    ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire,
};

var options = new ChatClientAgentOptions
{
    Name = "parallel-mcp-repro",
    AllowConcurrentInvocation = true,
    ChatOptions = new ChatOptions
    {
        ModelId = deployment,
        Instructions = "Issue independent tool calls together before waiting for results.",
        Tools = [learnMcp],
        AllowMultipleToolCalls = true,
    },
};

var agent = new AIProjectClient(endpoint, new DefaultAzureCredential())
    .AsAIAgent(options);

const string prompt = """
You must perform exactly two independent Microsoft Learn searches.
Issue both searches together in the same tool-call batch before waiting for either result.
Do not use the result of one search to formulate the other.
Search 1: Azure Functions Flex Consumption scaling.
Search 2: Azure Container Apps Jobs scaling.
After both complete, summarize each in one bullet.
""";

Console.WriteLine(await agent.RunAsync(prompt));

Error Messages / Stack Traces

No exception is thrown. The failure is behavioral: MCP lifecycle timestamps show that call 2 starts only after call 1 completes.

Attempt 1: call 1 = 3448-5547 ms; call 2 = 5570-7716 ms; gap = 23 ms
Attempt 2: call 1 = 1755-3669 ms; call 2 = 3674-5937 ms; gap = 5 ms
Attempt 3: call 1 = 1218-3340 ms; call 2 = 3349-5062 ms; gap = 9 ms

Parallel criterion: both distinct call IDs must enter in_progress before the earliest completed event.
Result: false in 3/3 attempts.

Package Versions

Microsoft.Agents.AI.Foundry: 1.15.0-preview.260722.1; Microsoft.Agents.AI.Foundry.Hosting: 1.15.0-preview.260722.1; Azure.AI.Projects: 2.1.0-beta.4; ModelContextProtocol: 2.1.0; Microsoft.Extensions.AI: 10.9.0

.NET Version

.NET 10.0

Additional Context

  • Model deployment: gpt-5.4-mini
  • MCP server: public Microsoft Learn MCP (https://learn.microsoft.com/api/mcp)
  • Tool: microsoft_docs_search
  • Approval mode: NeverRequire
  • Agent hosting protocol: OpenAI-compatible Responses
  • The response reports multiple tool calls enabled, and two distinct MCP call IDs are produced.
  • Timing is based on MCP lifecycle events (in_progress / completed), not inferred from final output ordering.
  • Relevant repository sample: dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools.

This may ultimately be a Foundry Responses/provider orchestration limitation rather than local Agent Framework scheduling. Filing here because the .NET API exposes both HostedMcpServerTool and concurrency controls without documenting whether hosted MCP calls can actually overlap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETUsage: [Issues, PRs], Target: .NetpythonUsage: [Issues, PRs], Target: PythontriageUsage: [Issues], Target: All issues that still need to be triaged

    Type

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions