Skip to content

Python: surface mid-run oauth_consent_request items from ResponsesHostServer - #7659

Draft
Giles Odigwe (giles17) wants to merge 1 commit into
microsoft:mainfrom
giles17:fix-hosting-oauth-consent
Draft

Python: surface mid-run oauth_consent_request items from ResponsesHostServer#7659
Giles Odigwe (giles17) wants to merge 1 commit into
microsoft:mainfrom
giles17:fix-hosting-oauth-consent

Conversation

@giles17

Copy link
Copy Markdown
Contributor

Motivation & Context

A customer running a hosted agent behind ResponsesHostServer with an on-behalf-of (OBO) MCP server never gets an OAuth consent card in Teams. The host logs Content type 'oauth_consent_request' is not supported yet. This is usually safe to ignore. and returns a completed response with no consent item, so the user has no way to authorize the delegated token and the agent can never call the tool.

They had already upgraded per #3950, which fixed the chat-client hop (_oauth_helpers.try_parse_oauth_consent_event now converts the upstream event into Content.from_oauth_consent_request(...)). The content is dropped one layer later, in the hosting layer, so no version bump of agent-framework-core/agent-framework-foundry helps.

_to_outputs in agent_framework_foundry_hosting/_responses.py has branches for text, reasoning, function call/result, image generation, MCP call/result, shell call/result, and function_approval_request, but none for oauth_consent_request, so that content hits the catch-all else and is discarded. The host already emits the item for connect-time consent failures (when _ensure_agent_ready() raises and consent_url_from_error finds a URL), and the inbound conversion in _output_item_to_message already understands the item type — the outbound mid-run half was simply missing. An OBO server that needs a per-user token at tool-invocation time connects fine and therefore never takes the connect-time path.

This is a different root cause from #7227, which is about parsing connect-time gateway errors whose source type is a2a_preview rather than mcp.

Description & Review Guide

  • What are the major changes?

    • _to_outputs gains an oauth_consent_request branch that emits response.output_item.added / .done for an OAuthConsentRequestOutputItem, carrying the consent link and a server_label read from the content's additional properties (defaulting to agent_framework). The link is validated as an absolute HTTPS URL; anything else is logged and skipped rather than emitted.
    • Both _handle_inner_agent and _handle_inner_workflow now terminate the response with response.incomplete (reason OAuth consent required for N tool(s).) instead of response.completed when at least one consent item was emitted mid-run, matching the existing connect-time behavior.
    • The item construction, link validation, and incomplete reason are factored into small module-level helpers (_emit_oauth_consent_item, _consent_link_from_content, _consent_server_label, _consent_incomplete_reason) that the pre-existing connect-time path now reuses, so both paths cannot drift apart.
    • New TestMidRunOAuthConsentSurfacing tests cover streaming, non-streaming, multiple consent contents in one update, and invalid links (empty, http://, non-URL) being skipped while the turn still completes.
  • What is the impact of these changes?

    • Clients now receive an actionable oauth_consent_request output item for mid-run consent, so a consent card can be rendered and the user can authorize and re-send the prompt. Automatic resumption of the interrupted turn remains separately tracked by Python: [Bug]: ResponsesHostServer has no turn suspension/resumption, so user must re-send message after OAuth consent #5594.
    • Behavior change for callers: a turn that produces a consent request now ends as incomplete rather than completed. This mirrors the connect-time path that already returned incomplete, and previously such a turn produced no output item at all.
    • No public API surface changes; all new helpers are private. The connect-time refactor is behavior-preserving and covered by the existing TestOAuthConsentSurfacing tests.
  • What do you want reviewers to focus on?

    • Whether response.incomplete is the right terminal status for a mid-run consent request, versus keeping completed and relying solely on the output item.

Related Issue

Fixes #7658

Contribution Checklist

  • The code builds clean without any errors or warnings
  • All unit tests pass, and I have added new tests where possible
  • The PR follows the Contribution Guidelines
  • This PR is linked to an issue and there is no other open PR for this issue (see Related Issue above).
  • This is not a breaking change. If it is a breaking change, add the breaking change label (or add "[BREAKING]" to the title prefix, before or after any language prefix) — a workflow keeps the label and title prefix in sync automatically.

…tServer

`_to_outputs` had no branch for `oauth_consent_request` content, so a consent
link produced after the agent was entered (for example by an on-behalf-of MCP
server that needs a per-user token at tool-invocation time) fell into the
catch-all and was dropped with "Content type 'oauth_consent_request' is not
supported yet". The client saw a completed response with no consent prompt.

Only connect-time consent failures raised by `_ensure_agent_ready` were
surfaced as `oauth_consent_request` output items, and the inbound conversion
(`_output_item_to_message`) already handled the item type, so the outbound
direction was the missing half.

- Emit `oauth_consent_request` added/done output items from `_to_outputs`,
  validating the link is an absolute HTTPS URL and reading `server_label` from
  the content's additional properties.
- End the response as `incomplete` (instead of `completed`) when a consent
  request was emitted mid-run, in both the agent and workflow handlers,
  matching the connect-time path.
- Factor the item emission, link validation, and incomplete reason into shared
  helpers reused by the connect-time path.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 992057d8-78a8-45a7-9201-35af0919b071
Copilot AI balanced review requested due to automatic review settings August 13, 2026 21:28
@agent-framework-automation agent-framework-automation Bot added the python Usage: [Issues, PRs], Target: Python label Aug 13, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Surfaces mid-run OAuth consent requests through Python’s Foundry Responses host.

Changes:

  • Emits OAuth consent output items and marks affected responses incomplete.
  • Adds consent-link validation and shared emission helpers.
  • Adds streaming and non-streaming tests.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

File Description
_responses.py Handles and emits mid-run OAuth consent requests.
test_responses.py Tests consent output, validation, and response status.
Suppressed comments (1)

python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py:214

  • The real Foundry OAuth parser does not populate additional_properties: agent_framework_foundry/_oauth_helpers.py:59-62 puts the upstream item (which carries server_label) in raw_representation. Consequently, actual mid-run OBO events always fall back to agent_framework; only this PR's synthetic test preserves the label. Fall back to the raw item's server_label so clients receive the originating server identity.
def _consent_server_label(content: Content) -> str:
    """Return the server label to report for an ``oauth_consent_request`` content."""
    label = content.additional_properties.get("server_label") if content.additional_properties else None
    return label if isinstance(label, str) and label else "agent_framework"

💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +180 to +183
parsed = urlparse(consent_link)
if parsed.scheme.lower() != "https" or not parsed.netloc:
logger.warning("Skipping oauth_consent_request with non-HTTPS consent_link.")
return None
Comment on lines +406 to +410
for event in _emit_oauth_consent_item(
response_event_stream,
context.response_id,
consent_error.consent_url,
consent_error.name,
Comment on lines +3836 to +3840
class TestMidRunOAuthConsentSurfacing:
"""A tool can require consent after the agent has been entered (e.g. an on-behalf-of
MCP server needing a per-user token), in which case the consent link arrives as
``oauth_consent_request`` content in the agent's stream rather than as a connect-time error.
"""
@github-actions

Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/foundry_hosting/agent_framework_foundry_hosting
   _responses.py7479787%145–146, 160, 163–164, 263, 278, 340, 399–402, 433–434, 437, 441, 474, 559, 562, 566, 632, 645, 661, 700–701, 1040, 1052, 1504–1505, 1509, 1554, 1556, 1558, 1560, 1564, 1572, 1575–1579, 1581, 1591, 1595, 1608, 1642–1647, 1651–1652, 1660–1666, 1696–1697, 1699–1700, 1702, 1707, 1715–1716, 1718, 1723–1727, 1729, 1736–1737, 1739–1740, 1746, 1748–1752, 1762, 1768, 1792, 1811, 1817, 1819, 1821–1824, 1832, 1834
TOTAL45999426090% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
9363 36 💤 0 ❌ 0 🔥 2m 20s ⏱️

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MAF Automated Review — Iteration 1

Result: Findings reported
Scope: full PR (1 commit(s)): b452dd73d248
Model: gpt-5.6-sol

Overview

The change consistently surfaces valid mid-run OAuth consent requests as output items and terminates affected agent and workflow responses as incomplete, with coverage for streaming, non-streaming, multiple requests, and common invalid links. Shared emit/reason helpers also keep the connect-time and mid-run paths aligned. Two residual gaps remain: malformed HTTPS syntax can fail the whole response, and real Foundry events do not propagate their required server label through the new hosting conversion.

Reviewed the supplied pull-request change set across correctness, security/reliability, architecture, and failure behavior.
2 verified findings remained after source verification (2 medium) across 1 file. Details are attached to the affected lines below.

Affected areas: python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py

if not consent_link:
logger.warning("Received oauth_consent_request content without a consent_link; skipping.")
return None
parsed = urlparse(consent_link)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Malformed HTTPS syntax such as https://[ makes urlparse raise ValueError. Because this helper runs while consuming the agent stream, that turns an invalid consent item into response.failed instead of skipping it and allowing the turn to complete as the helper contract and invalid-link behavior require. Please handle parse/hostname validation errors and return None for every malformed URL.


def _consent_server_label(content: Content) -> str:
"""Return the server label to report for an ``oauth_consent_request`` content."""
label = content.additional_properties.get("server_label") if content.additional_properties else None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real Foundry OAuth events never populate this property: try_parse_oauth_consent_event stores the source item, including its required server_label, only in content.raw_representation. As a result, production requests are always re-emitted as agent_framework, losing the MCP server identity even though the synthetic test passes. Please preserve the label during parsing or read the validated label from the raw item here, and cover the parser-to-hosting path.

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

Labels

python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: ResponsesHostServer drops mid-run oauth_consent_request content ('not supported yet'), consent link never reaches the client

2 participants