Skip to content

Python: [Bug]: Hosted toolbox MCP call_id from a previous turn is replayed as function_call_output without its function_call (400 from Responses API) #5546

@cristofima

Description

@cristofima

Description

What happened?

When a Foundry-hosted agent uses a toolbox MCP tool that completed successfully on a previous turn within the same conversation_id, the next turn fails with a 400 from the Azure OpenAI Responses API:

openai.BadRequestError: Error code: 400 - {
  'error': {
    'message': "No tool call found for function call output with call_id mcp_06b686e11f118cf40169f0e5badb3081979842929d5cf04920.",
    'type': 'invalid_request_error',
    'param': 'input',
    'code': None
  }
}

The error is raised inside agent_framework_openai/_chat_client.py:555 when calling client.responses.create(stream=True, **run_options). The input array sent to the Responses API contains a function_call_output whose call_id has no matching function_call in the same array, which violates the Responses API contract. The 400 happens before the model is invoked.

The same conversation_id is reused across turns, while caresp_* ids change. A retry of the failing prompt produces the exact same 400 with the same orphan call_id, which confirms the orphan is now persisted in conversation state and is replayed every time.

What did you expect to happen?

A subsequent user message in the same conversation should be processed normally. Either:

  • the framework should not replay a function_call_output from a prior turn without its matching function_call in the same input payload, or
  • the hosted MCP tool calls should be persisted as a balanced pair (call + output) so the Responses API accepts them on replay.

Steps to reproduce the issue

  1. Build a Foundry hosted agent with agent-framework-foundry-hosting==1.0.0a260428 and agent-framework-core==1.2.1 / agent-framework-foundry==1.2.1.
  2. Wire a Foundry Toolbox MCP connector backed by an OAuth-protected MCP server (in our case Logic Apps with Entra ID auth code + PKCE).
  3. From the Foundry playground, send three prompts in the same conversation:
    • Prompt 1: a question that does not require the tool. Succeeds.
    • Prompt 2: a question that triggers the MCP tool. Returns oauth_consent_request. After the user authorizes and resends, the tool runs and returns the result. Succeeds.
    • Prompt 3: any follow-up question. Fails with the 400 above before the model streams anything.

Code Sample

Minimal shape of the agent wiring (full project is a Foundry hosted agent, but the failure is reproducible with this skeleton):


from agent_framework_foundry import FoundryChatClient
from agent_framework_foundry_hosting import ResponsesHostServer, InMemoryResponseProvider
from azure.identity.aio import ManagedIdentityCredential

async def main() -> None:
    async with (
        ManagedIdentityCredential() as credential,
        FoundryChatClient(
            project_endpoint=PROJECT_ENDPOINT,
            model=MODEL_DEPLOYMENT_NAME,
            credential=credential,
        ).as_agent(
            name="agent-baseline-python",
            instructions=SYSTEM_PROMPT,
            tools=[mcp_toolbox_tool],   # Foundry Toolbox MCP connector (OAuth-protected)
            default_options={
                "store": False,
                "tool_choice": "required",
                "temperature": 0.2,
                "max_tokens": 2000,
            },
        ) as agent,
    ):
        server = ResponsesHostServer(agent, store=InMemoryResponseProvider())
        await server.run_async()


Note: `default_options={"store": False, ...}` is set on the agent, but the hosting layer still creates the response with `store=True` (visible in the host log below), so the conversation state is persisted server-side and replayed on every subsequent turn.

Error Messages / Stack Traces

Relevant slice of the host log (timestamps and ids preserved):


2026-04-28 16:51:33  Creating response caresp_...E2VI1...  conversation_id=conv_24fe0da107637088004mYJr8gW9lTSNP9jBYudLITxlXvLk9Kp  store=True
2026-04-28 16:51:40  POST /responses 200 (turn 1, no tool, ok)
2026-04-28 16:52:07  Creating response caresp_...QC68...  conversation_id=conv_24fe0...4mYJr...  store=True
2026-04-28 16:52:19  POST /responses 200 (turn 2, after consent + retry, tool ran, ok)
2026-04-28 16:55:33  Creating response caresp_...Yj9D...  conversation_id=conv_24fe0...4mYJr...  store=True
2026-04-28 16:55:34  ERROR  Handler raised after response.created


Full stack of the failing turn:


Traceback (most recent call last):
  File "/app/.venv/lib/python3.12/site-packages/agent_framework_openai/_chat_client.py", line 555, in _stream
    async for chunk in await client.responses.create(stream=True, **run_options):
  File "/app/.venv/lib/python3.12/site-packages/openai/resources/responses/responses.py", line 2565, in create
    return await self._post(
  File "/app/.venv/lib/python3.12/site-packages/openai/_base_client.py", line 1884, in post
    return await self.request(cast_to, opts, stream=stream, stream_cls=stream_cls)
  File "/app/.venv/lib/python3.12/site-packages/openai/_base_client.py", line 1669, in request
    raise self._make_status_error_from_response(err.response) from None
openai.BadRequestError: Error code: 400 - {'error': {'message': 'No tool call found for function call output with call_id mcp_06b686e11f118cf40169f0e5badb3081979842929d5cf04920.', 'type': 'invalid_request_error', 'param': 'input', 'code': None}}

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/app/.venv/lib/python3.12/site-packages/azure/ai/agentserver/responses/hosting/_orchestrator.py", line 1243, in _process_handler_events
    async for raw in _iter_with_winddown(handler_iterator, ctx.cancellation_signal):
  File "/app/.venv/lib/python3.12/site-packages/azure/ai/agentserver/responses/hosting/_orchestrator.py", line 156, in _iter_with_winddown
    item = await aiter.__anext__()
  File "/app/.venv/lib/python3.12/site-packages/azure/ai/agentserver/responses/hosting/_routing.py", line 347, in _await_and_normalize
    async for event in self._normalize_handler_result(inner):
  File "/app/.venv/lib/python3.12/site-packages/agent_framework_foundry_hosting/_responses.py", line 230, in _handle_inner_agent
    async for update in self._agent.run(stream=True, **run_kwargs):
  File "/app/.venv/lib/python3.12/site-packages/agent_framework/_types.py", line 3015, in __anext__
    update: UpdateT = await self._iterator.__anext__()
  File "/app/.venv/lib/python3.12/site-packages/agent_framework/_tools.py", line 2486, in _stream
    async for update in inner_stream:
  File "/app/.venv/lib/python3.12/site-packages/agent_framework_openai/_chat_client.py", line 563, in _stream
    self._handle_request_error(ex)
  File "/app/.venv/lib/python3.12/site-packages/agent_framework_openai/_chat_client.py", line 494, in _handle_request_error
    raise ChatClientException(
agent_framework.exceptions.ChatClientException: ("<class '__main__._AzureOpenAIFoundryChatClient'> service failed to complete the prompt: Error code: 400 - {'error': {'message': 'No tool call found for function call output with call_id mcp_06b686e11f118cf40169f0e5badb3081979842929d5cf04920.', 'type': 'invalid_request_error', 'param': 'input', 'code': None}}", BadRequestError(...))


The retry 20 seconds later fails with the exact same `call_id`:


2026-04-28 16:55:55  same trace, same call_id mcp_06b686e11f118cf40169f0e5badb3081979842929d5cf04920, same 400

Package Versions

agent-framework-core: 1.2.1, agent-framework-foundry: 1.2.1, agent-framework-openai: 1.2.1, agent-framework-foundry-hosting: 1.0.0a260428, mcp: >=1.24,<2, azure-ai-agentserver-core: 2.0.0b3, azure-ai-agentserver-responses: 1.0.0b5,

Python Version

Python 3.12

Additional Context

  • The MCP tool is a Foundry Toolbox connector pointing to a Logic Apps MCP server protected by Entra ID (auth code + PKCE). The agent forwards the caller bearer token via a custom ASGI middleware; service identity is only used during MCP startup. None of that path is involved in the failing turn, the 400 is raised before the model call goes out.
  • Turn 2 includes an OAuth consent loop. We surface oauth_consent_request to the user via a FoundryChatClient subclass that overrides _parse_chunk_from_openai, since the playground does not render that event natively (related to Python: [Bug]: ResponsesHostServer drops oauth_consent_request and function_approval_request from toolbox MCP connectors #5535). The tool eventually executes successfully on turn 2.
  • We have store=False in default_options per project guidance, but the hosting layer logs store=True regardless, so the conversation state appears to be persisted server-side and replayed on every subsequent turn.
  • Storage on the host side is the default InMemoryResponseProvider from agent-framework-foundry-hosting.
  • Happy to share a fuller HAR / OTel export privately if helpful.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingpython

Type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions