Skip to content

feat(responses): interrupt approval-required MCP tool calls - #1174

Merged
slin1237 merged 1 commit into
mainfrom
feat/responses-approval-interrupt
Apr 18, 2026
Merged

slin1237 merged 1 commit into
mainfrom
feat/responses-approval-interrupt

Conversation

@zhaowenzi

@zhaowenzi zhaowenzi commented Apr 17, 2026 •

Copy link
Copy Markdown
Contributor

Description

Problem

SMG's Responses API MCP flow did not expose approval-required interruptions in a way that matches OpenAI. Even when a tool required approval, the router continued to emit mcp_call output instead of stopping at an approval request. The protocol layer also could not round-trip OpenAI's richer require_approval shape.

Solution

This PR adds the protocol and MCP core support needed for approval-aware execution, then wires the OpenAI non-streaming Responses path to stop on pending approval and emit mcp_approval_request instead of continuing to mcp_call.

Current scope note: the router interruption behavior in this PR is intentionally limited to explicit require_approval: \"always\". Object-form require_approval rules are supported at the protocol round-trip level, but their runtime semantics are left to a follow-up PR. The non-streaming interruption behavior is also scoped per tool call rather than blanket-enabling interactive approval for the whole request.

Changes

  • extend require_approval in crates/protocols/src/responses.rs to support both string and object forms
  • add protocol round-trip tests for require_approval
  • expose pending approval from MCP core via ToolExecutionResult and configurable session approval mode
  • keep legacy MCP execution helpers working by flattening pending approval only for old callers
  • update OpenAI non-streaming Responses handling to use interactive approval only for MCP tool calls that explicitly request require_approval: \"always\"
  • keep a single MCP session for the request and record approval mode per exposed tool, selecting approval behavior per tool call
  • interrupt the non-streaming MCP tool loop with mcp_approval_request output instead of emitting mcp_call
  • leave object-form require_approval rule semantics for a follow-up PR
  • add router/integration coverage for approval-required interruption
  • add e2e coverage for the OpenAI cloud Responses path, with follow-up notes to extend the same test for approval continuation later

Test Plan

cargo run --bin smg -- --enable-igw --port 9999

In another shell, register an OpenAI worker without inlining secrets in the command:

curl -X POST http://localhost:9999/workers \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://api.openai.com",
    "api_key": "<OPENAI_API_KEY>",
    "runtime": "external",
    "disable_health_check": true
  }'

Then send a mixed-tool Responses request where one MCP tool should execute immediately and another should interrupt for approval:

curl http://localhost:9999/v1/responses \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.4",
    "store": false,
    "tools": [
      {
        "type": "mcp",
        "server_label": "deepwiki",
        "server_url": "https://mcp.deepwiki.com/mcp",
        "require_approval": "never"
      },
      {
        "type": "mcp",
        "server_label": "openai-developer-docs",
        "server_url": "https://developers.openai.com/mcp",
        "require_approval": "always"
      }
    ],
    "input": "First use deepwiki to answer what transport protocols are supported by the 2025-03-26 MCP specification in one short sentence. Then use openai developer docs to find the Responses API approvals documentation and summarize it in one sentence."
  }'

Expected behavior for this PR:

{
  "status": "completed",
  "output": [
    {
      "type": "mcp_list_tools",
      "server_label": "deepwiki"
    },
    {
      "type": "mcp_list_tools",
      "server_label": "openai-developer-docs"
    },
    {
      "type": "mcp_call",
      "id": "mcp_...",
      "server_label": "deepwiki",
      "name": "ask_question",
      "arguments": "{...}",
      "output": ...
    },
    {
      "type": "mcp_approval_request",
      "id": "mcpr_...",
      "server_label": "openai-developer-docs",
      "name": "search_openai_docs",
      "arguments": "{...}"
    }
  ]
}

The response should allow the deepwiki tool to emit mcp_call, then stop at openai-developer-docs's mcp_approval_request without emitting an openai-developer-docs mcp_call yet.

This PR does not yet validate object-form require_approval rules at runtime; that behavior is deferred to a follow-up PR.

Checklist
  • cargo +nightly fmt passes
  • cargo clippy --all-targets --all-features -- -D warnings passes
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg to discuss, review, and merge PRs

Summary by CodeRabbit

  • New Features

    • MCP tool approval workflow: executions can pause and return an approval request (stopping execution) and later resume.
    • Per-tool approval configuration: require-approval can be set per tool and filtered by mode or rules; non-streaming calls honor these settings.
    • Require-approval accepts either simple string or structured object forms.
  • Tests

    • Added integration and e2e tests validating approval interruption behavior and serde round-trips.

@github-actions github-actions Bot added mcp MCP related changes tests Test changes protocols Protocols crate changes model-gateway Model gateway crate changes openai OpenAI router changes labels Apr 17, 2026
@coderabbitai

coderabbitai Bot commented Apr 17, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Introduces approval-preserving tool execution: adds ToolExecutionResult and PendingToolExecution, refactors orchestrator/session to return and propagate pending-approval state, updates protocol approval types, and integrates approval-aware interruption/response construction into the non-streaming router tool loop.

Changes

Cohort / File(s) Summary
Module Exports
crates/mcp/src/core/mod.rs, crates/mcp/src/lib.rs
Re-exported ToolExecutionResult and PendingToolExecution from the mcp crate root.
Core Orchestrator
crates/mcp/src/core/orchestrator.rs
Added public ToolExecutionResult enum and PendingToolExecution struct; added into_output() and execute_tool_resolved_result(...); refactored helpers to return approval-aware results and adjusted success/metrics logic.
Session Logic
crates/mcp/src/core/session.rs
Session now stores request_id/tenant_ctx, per-tool approval_mode, new APIs execute_tool_result(s)(...) and configure_response_tools_approval(...), and routes through approval-aware orchestrator calls while preserving pending state.
Protocol Types
crates/protocols/src/responses.rs
Changed RequireApproval to #[serde(untagged)] with Mode/Rules; added RequireApprovalMode, RequireApprovalRules, RequireApprovalFilter; added serde round-trip tests.
Router / Tool Loop
model_gateway/src/routers/openai/mcp/tool_loop.rs, model_gateway/src/routers/openai/responses/non_streaming.rs
Tool loop switched to execute_tool_result(...); handles PendingApproval by building mcp_approval_request and returning a completed response with retained items; session approval config applied during non-streaming handling.
Tests & E2E
e2e_test/responses/test_tools_call.py, model_gateway/tests/api/responses_api_test.rs
Added e2e test and assertion helper for approval interruption; added non-streaming integration test asserting mcp_approval_request emission; updated tests to use RequireApproval::Mode(...) forms.

Sequence Diagram

sequenceDiagram
    participant Client as Client
    participant Router as Router (tool_loop)
    participant Session as McpToolSession
    participant Orchestrator as McpOrchestrator
    participant Approval as Approval/Eval

    Client->>Router: request (with tools)
    Router->>Session: execute_tool_result(input)
    Session->>Orchestrator: execute_tool_resolved_result(input, server_key, server_label, ctx)
    Orchestrator->>Approval: evaluate execution / approval rules
    alt Approval Required
        Approval-->>Orchestrator: PendingApproval
        Orchestrator-->>Session: ToolExecutionResult::PendingApproval
        Session-->>Router: PendingApproval
        Router->>Router: build_approval_response(mcp_approval_request + retained items)
        Router-->>Client: response (status: completed, mcp_approval_request item)
    else Executed
        Approval-->>Orchestrator: Executed(output)
        Orchestrator-->>Session: ToolExecutionResult::Executed
        Session-->>Router: Executed
        Router-->>Client: response containing mcp_call output
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • CatherineSue
  • key4ng
  • XinyueZhang369

Poem

🐰 I hopped through code to pause a call,
Pending hops for tools, large or small.
If approval’s needed, I wait by the gate,
Else I leap and let the outputs skate.
Carrots and tests — the flow’s first-rate!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding support for interrupting MCP tool calls when approval is required.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/responses-approval-interrupt

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a mechanism for MCP tools to require interactive approval before execution, allowing the tool loop to interrupt and return an mcp_approval_request. Key changes include the addition of a ToolExecutionResult enum to distinguish between executed and pending states, updates to the McpOrchestrator and McpToolSession to preserve this state, and an expansion of the RequireApproval protocol to support granular rules based on tool names and read-only status. Feedback focuses on ensuring that pending approvals are not incorrectly recorded as failures in metrics and that tool names in approval requests are properly escaped and use model-facing aliases.

Comment thread crates/mcp/src/core/orchestrator.rs Outdated
Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs Outdated
Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs Outdated
Comment thread crates/mcp/src/core/orchestrator.rs Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Solid implementation. The protocol extension, MCP core refactoring, and non-streaming wiring are clean and well-tested. Two nits flagged inline (exposed vs resolved tool name in approval request, metrics semantics for pending approval). No blocking issues.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a806806730

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread model_gateway/src/routers/openai/responses/non_streaming.rs Outdated
Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mcp/src/core/orchestrator.rs`:
- Around line 1097-1131: The metrics currently treat a PendingApproval result as
a failed execution because succeeded is computed only for
ToolExecutionResult::Executed and record_call_end is always called; update the
logic so that when ApprovalExecutionResult::PendingApproval produces a
ToolExecutionResult::PendingApproval you do not call
self.metrics.record_call_end (or call a new
self.metrics.record_call_pending_approval) and only invoke record_call_end when
execution actually completed (i.e., when producing
ToolExecutionResult::Executed); refer to the PendingApproval construction in
this block, record_call_end usage, and note that record_approval_requested is
already emitted deeper in execute_tool_with_approval_raw_internal if you prefer
the skip-to-avoid-double-counting approach.

In `@model_gateway/src/routers/openai/mcp/tool_loop.rs`:
- Around line 842-858: The approval request is using the orchestrator-resolved
internal name (pending.approval_request.tool_name) instead of the exposed name,
causing client-visible internal names and mismatches with function_call.name and
call.arguments; update the call to build_mcp_approval_request_item to pass
&pending.tool_name (the exposed name set by McpToolSession::execute_tool_result)
instead of &pending.approval_request.tool_name so the approval item and
subsequent build_approval_response use the externally visible tool name.

In `@model_gateway/src/routers/openai/responses/non_streaming.rs`:
- Around line 79-94: The approval_mode detection currently treats any
RequireApproval::Rules as Interactive; change it so only explicit string modes
drive Interactive sessions until rule-filter semantics are implemented: update
the predicate that inspects original_body.tools / ResponseTool::Mcp to only
return ApprovalMode::Interactive when mcp_tool.require_approval.as_ref() matches
Some(RequireApproval::Mode(mode)) and mode != RequireApprovalMode::Never; leave
Rules variants as non-interactive (PolicyOnly) for now, or alternatively
implement a helper like evaluate_require_approval_rules(&rules) and call it from
this predicate if you prefer to fully evaluate Rules before choosing
Interactive.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b56c7dd0-b5a7-4751-86c6-9520e851dfa8

📥 Commits

Reviewing files that changed from the base of the PR and between 09c9353 and a806806.

📒 Files selected for processing (9)
  • crates/mcp/src/core/mod.rs
  • crates/mcp/src/core/orchestrator.rs
  • crates/mcp/src/core/session.rs
  • crates/mcp/src/lib.rs
  • crates/protocols/src/responses.rs
  • e2e_test/responses/test_tools_call.py
  • model_gateway/src/routers/openai/mcp/tool_loop.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/tests/api/responses_api_test.rs

Comment thread crates/mcp/src/core/orchestrator.rs
Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs
Comment thread model_gateway/src/routers/openai/responses/non_streaming.rs Outdated
@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from a806806 to 63966a3 Compare April 17, 2026 06:52

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 63966a3415

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread model_gateway/src/routers/openai/responses/non_streaming.rs Outdated
Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs Outdated
@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from 63966a3 to ba17a0c Compare April 17, 2026 07:24

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ba17a0c54c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread model_gateway/src/routers/openai/mcp/tool_loop.rs Outdated
@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from ba17a0c to 8173c59 Compare April 17, 2026 08:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 8173c59a9b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/mcp/src/core/session.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
crates/mcp/src/core/session.rs (1)

95-103: ⚠️ Potential issue | 🔴 Critical

Don't replace the caller's tenant with TenantContext::default().

Every per-tool context now comes from request_ctx_for(), so this constructor value is what execute_tool_with_approval_raw_internal() passes into ApprovalParams.tenant_ctx. Hardcoding the default tenant will run approval policy/audit under the wrong tenant for every non-default request.

Proposed fix
 pub fn new(
     orchestrator: &'a McpOrchestrator,
     mcp_servers: Vec<McpServerBinding>,
     request_id: impl Into<String>,
+    tenant_ctx: TenantContext,
 ) -> Self {
     let request_id = request_id.into();
-    let tenant_ctx = TenantContext::default();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mcp/src/core/session.rs` around lines 95 - 103, The constructor
currently overwrites the caller's tenant by using TenantContext::default();
change the signature of McpSession::new to accept a TenantContext (e.g.,
tenant_ctx: TenantContext) and assign that to tenant_ctx instead of
TenantContext::default(), and update call sites (such as where
execute_tool_with_approval_raw_internal() / request_ctx_for() create sessions)
to pass the caller's request context so ApprovalParams.tenant_ctx remains the
original request tenant rather than the default.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mcp/src/core/session.rs`:
- Around line 251-258: The PendingApproval branch updates pending.tool_name but
not the nested pending.approval_request.tool_name, leaving the embedded approval
request with the old pre-remap name; in the ToolExecutionResult::PendingApproval
arm (where you already set pending.tool_name = invoked_name), also assign
pending.approval_request.tool_name = invoked_name so the
exposed/collision-suffixed name stays in sync with the embedded
mcp_approval_request; ensure you reference the
ToolExecutionResult::PendingApproval variant, the pending variable, its
tool_name field, and the nested approval_request.tool_name when making this
change.

In `@e2e_test/responses/test_tools_call.py`:
- Around line 220-225: The test is over-constraining the sequence by requiring a
deepwiki mcp_call before approval items; instead, relax the assertions to only
check presence regardless of order: use resp.output and output_types to assert
that "mcp_call" and "mcp_approval_request" exist, assert that any item in
resp.output has type "mcp_call" with server_label == "deepwiki" (i.e., ensure
deepwiki_calls exists) without asserting positional ordering, and apply the same
relaxation to the other affected blocks referenced (the checks around lines
242-254 and 570-591) so the test only verifies presence of required call types
and labels rather than strict ordering.

---

Outside diff comments:
In `@crates/mcp/src/core/session.rs`:
- Around line 95-103: The constructor currently overwrites the caller's tenant
by using TenantContext::default(); change the signature of McpSession::new to
accept a TenantContext (e.g., tenant_ctx: TenantContext) and assign that to
tenant_ctx instead of TenantContext::default(), and update call sites (such as
where execute_tool_with_approval_raw_internal() / request_ctx_for() create
sessions) to pass the caller's request context so ApprovalParams.tenant_ctx
remains the original request tenant rather than the default.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0038f9ec-d427-4de5-98fe-e0c846404aaa

📥 Commits

Reviewing files that changed from the base of the PR and between a806806 and 8173c59.

📒 Files selected for processing (9)
  • crates/mcp/src/core/mod.rs
  • crates/mcp/src/core/orchestrator.rs
  • crates/mcp/src/core/session.rs
  • crates/mcp/src/lib.rs
  • crates/protocols/src/responses.rs
  • e2e_test/responses/test_tools_call.py
  • model_gateway/src/routers/openai/mcp/tool_loop.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/tests/api/responses_api_test.rs

Comment thread crates/mcp/src/core/session.rs
Comment thread e2e_test/responses/test_tools_call.py
@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from 8173c59 to 3968cb7 Compare April 17, 2026 08:42

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3968cb7cfa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/mcp/src/core/session.rs
Comment thread crates/mcp/src/core/session.rs Outdated
@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from 3968cb7 to 7ff04eb Compare April 17, 2026 15:11

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7ff04eb852

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/mcp/src/core/session.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
model_gateway/src/routers/openai/mcp/tool_loop.rs (1)

776-862: ⚠️ Potential issue | 🟡 Minor

Confirm: parallel MCP function_calls after the approval-triggering call are intentionally dropped in this iteration.

The current implementation processes function_calls sequentially in a loop and early-returns via build_approval_response(...) when the first call triggers PendingApproval. Remaining calls in that iteration are skipped, never executed, and never recorded or surfaced to the client.

This is intentional scope for v1 ("one approval per request"), consistent with the design noted in PR #1174. However, this is incomplete work: test_mcp_approval_required_interrupts_and_resumes in e2e_test/responses/test_tools_call.py (line 580) includes an explicit TODO to extend approval-resumption testing in a follow-up PR. The session layer supports concurrent execution with approval state preservation (execute_tool_results with buffered() in crates/mcp/src/core/session.rs), but the non-streaming tool loop does not leverage it.

If parallel MCP calls with approval are expected in production, either pre-execute non-approval calls before the early return, or emit subsequent calls as additional output items so the client is aware of them. If this is deferred, document the limitation explicitly in a code comment or tracked issue.

♻️ Duplicate comments (1)
crates/protocols/src/responses.rs (1)

83-112: ⚠️ Potential issue | 🟠 Major

Reject filtered require_approval objects until execution actually honors them.

RequireApproval::Rules now deserializes successfully, but the runtime path still only upgrades RequireApproval::Mode(RequireApprovalMode::Always) to interactive approval. Requests like {"never":{"tool_names":["foo"]}} will therefore round-trip here and then execute with PolicyOnly, silently dropping the caller’s approval policy.

🛠️ Suggested guard until the follow-up lands
 fn validate_response_tools(tools: &[ResponseTool]) -> Result<(), ValidationError> {
     // MCP server_label must be present and unique (case-insensitive).
     let mut seen_mcp_labels: HashSet<String> = HashSet::new();

     for (idx, tool) in tools.iter().enumerate() {
         if let ResponseTool::Mcp(mcp) = tool {
+            if matches!(mcp.require_approval, Some(RequireApproval::Rules(_))) {
+                let mut e = ValidationError::new("unsupported_require_approval");
+                e.message = Some(
+                    "Object-form 'require_approval' is not supported yet; use \"always\" or \"never\"."
+                        .into(),
+                );
+                return Err(e);
+            }
+
             let raw_label = mcp.server_label.as_str();
             if raw_label.is_empty() {
                 let mut e = ValidationError::new("missing_required_parameter");
                 e.message = Some(
                     format!("Missing required parameter: 'tools[{idx}].server_label'.").into(),

Based on learnings, RequireApproval intentionally only supported string forms until the object-form filtering behavior was implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/protocols/src/responses.rs` around lines 83 - 112, The new object-form
`RequireApprovalRules` must be rejected at deserialization until execution
honors filtering: change RequireApproval's deserialization to fail when the
`Rules` (RequireApprovalRules) shape is encountered so callers cannot round-trip
an unsupported policy. Implement a custom Deserialize for the RequireApproval
enum (or a deserialize_with for the enum) that successfully parses the
string-mode forms into RequireApproval::Mode(RequireApprovalMode::...) but
returns a clear error when an object matching RequireApprovalRules is provided;
reference the types RequireApproval, RequireApprovalMode, and
RequireApprovalRules to locate and update the deserialization logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@crates/protocols/src/responses.rs`:
- Around line 83-112: The new object-form `RequireApprovalRules` must be
rejected at deserialization until execution honors filtering: change
RequireApproval's deserialization to fail when the `Rules`
(RequireApprovalRules) shape is encountered so callers cannot round-trip an
unsupported policy. Implement a custom Deserialize for the RequireApproval enum
(or a deserialize_with for the enum) that successfully parses the string-mode
forms into RequireApproval::Mode(RequireApprovalMode::...) but returns a clear
error when an object matching RequireApprovalRules is provided; reference the
types RequireApproval, RequireApprovalMode, and RequireApprovalRules to locate
and update the deserialization logic.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1206f3eb-50eb-468d-a634-05b5d903c20a

📥 Commits

Reviewing files that changed from the base of the PR and between 8173c59 and 7ff04eb.

📒 Files selected for processing (9)
  • crates/mcp/src/core/mod.rs
  • crates/mcp/src/core/orchestrator.rs
  • crates/mcp/src/core/session.rs
  • crates/mcp/src/lib.rs
  • crates/protocols/src/responses.rs
  • e2e_test/responses/test_tools_call.py
  • model_gateway/src/routers/openai/mcp/tool_loop.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/tests/api/responses_api_test.rs

@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from 7ff04eb to 66ffeeb Compare April 17, 2026 15:22
Comment thread model_gateway/src/routers/openai/responses/non_streaming.rs
Signed-off-by: Ziwen Zhao <zzw.mose@gmail.com>
slin1237 added a commit that referenced this pull request Apr 18, 2026
…ector

Both have been the consistent primary authors of recent PRs in these subsystems:

- @zhoug9127 (Daisy): #1168, #1149, #1065, #1061, #976 — mcp + data_connector
- @zhaowenzi (Ziwen): #1174, #1163, #1123 — mcp

Signed-off-by: Simo Lin <linsimo.mark@gmail.com>
@zhaowenzi
zhaowenzi force-pushed the feat/responses-approval-interrupt branch from 66ffeeb to 6774fb3 Compare April 18, 2026 00:05
@zhaowenzi
zhaowenzi requested a review from zhoug9127 as a code owner April 18, 2026 00:05

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6774fb3e4b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/mcp/src/core/session.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
crates/protocols/src/responses.rs (1)

83-112: ⚠️ Potential issue | 🟠 Major

Do not accept rule-form approvals until they are enforced.

RequireApproval::Rules now deserializes valid object-form requests, but the runtime path still only treats RequireApproval::Mode(RequireApprovalMode::Always) as interactive (crates/mcp/src/core/session.rs:309-314); Rules falls through as policy-only. A client sending {"always": ...} can therefore request approval while the tool executes without an approval interruption. Please either reject Rules during request validation for now, or evaluate/map it conservatively before execution.

Based on learnings, RequireApproval intentionally only supported string forms until object-form filtering behavior was implemented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/protocols/src/responses.rs` around lines 83 - 112, The PR added an
object-form variant RequireApproval::Rules but the runtime approval check still
only treats RequireApproval::Mode(RequireApprovalMode::Always) as interactive,
so a client can send {"always": ...} and bypass interactive approval; fix by
either (A) rejecting Rules at request/validation time (return a validation error
when RequireApproval::Rules is present) or (B) conservatively mapping Rules to
interactive behavior before execution (treat RequireApproval::Rules the same as
RequireApproval::Mode(RequireApprovalMode::Always) in the session approval
decision path). Locate the enum RequireApproval and the runtime code that
matches on RequireApproval in the session approval check (the session approval
decision path in core session code) and implement one of these two fixes
consistently.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@e2e_test/responses/test_tools_call.py`:
- Around line 561-583: The test named
test_mcp_approval_required_interrupts_and_resumes only asserts the interruption
and does not perform the resume flow; either rename it to reflect
interruption-only behavior (e.g., test_mcp_approval_required_interrupts_only) or
implement the continuation: after
assert_mcp_approval_interruption_non_streaming(resp) call the
approval/continuation API flow using api_client (the same client used earlier)
to send the approval token/continuation request for
DEEPWIKI_MCP_TOOL/BRAVE_MCP_TOOL_REQUIRE_APPROVAL_ALWAYS, then assert the
resumed response emits the expected mcp_call and final assistant output
(reuse/assert with the existing helper expectations such as checking for
mcp_call events and final assistant text) so the test name matches its behavior.

---

Duplicate comments:
In `@crates/protocols/src/responses.rs`:
- Around line 83-112: The PR added an object-form variant RequireApproval::Rules
but the runtime approval check still only treats
RequireApproval::Mode(RequireApprovalMode::Always) as interactive, so a client
can send {"always": ...} and bypass interactive approval; fix by either (A)
rejecting Rules at request/validation time (return a validation error when
RequireApproval::Rules is present) or (B) conservatively mapping Rules to
interactive behavior before execution (treat RequireApproval::Rules the same as
RequireApproval::Mode(RequireApprovalMode::Always) in the session approval
decision path). Locate the enum RequireApproval and the runtime code that
matches on RequireApproval in the session approval check (the session approval
decision path in core session code) and implement one of these two fixes
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 50e52621-63d1-4c1b-b659-834980bebe89

📥 Commits

Reviewing files that changed from the base of the PR and between 7ff04eb and 6774fb3.

📒 Files selected for processing (9)
  • crates/mcp/src/core/mod.rs
  • crates/mcp/src/core/orchestrator.rs
  • crates/mcp/src/core/session.rs
  • crates/mcp/src/lib.rs
  • crates/protocols/src/responses.rs
  • e2e_test/responses/test_tools_call.py
  • model_gateway/src/routers/openai/mcp/tool_loop.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/tests/api/responses_api_test.rs

Comment thread e2e_test/responses/test_tools_call.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mcp MCP related changes model-gateway Model gateway crate changes openai OpenAI router changes protocols Protocols crate changes tests Test changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants