Conversation
|
Missing Signed-off-by: 8998839. All commits require sign-off (via |
Praxis connector_id values are pipeline-local, so deferred MCP entries are resolved in-memory, stripped from the backend body, and listed only after a tool_search_call instead of forwarding internal IDs to inference. Signed-off-by: mkoushni <mkoushni@redhat.com>
8998839 to
c3779ff
Compare
Keep deferred MCP connector coverage alongside main's streaming IRR tests. Signed-off-by: mkoushni <mkoushni@redhat.com>
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com>
praxis-bot
left a comment
There was a problem hiding this comment.
praxis-bot review: feat(openai): support deferred resolution for configured MCP connectors
The security boundary is well-implemented: connector_id, server_url, authorization, and headers are stripped from the outbound body via an allowlist sanitizer (sanitize_deferred_connector_tool), and the transactional discovery design (list all connectors, check collisions and size, then commit) prevents partial state corruption on failure.
Pipeline ordering between openai_mcp_dispatch (runs before openai_agentic_loop in request phase) is load-bearing for correctness -- mcp_dispatch must observe tool_search_calls before prepare_iteration clears them. The YAML config comments document this, which is good.
One finding below.
Adding deferred_mcp to the Default inventory pushed cognitive_complexity over 25. Signed-off-by: mkoushni <mkoushni@redhat.com>
Keep the three call-type vectors reset together so stale tool_search_calls cannot survive a non-dispatchable or error stream round. Signed-off-by: mkoushni <mkoushni@redhat.com>
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com> # Conflicts: # apis/src/openai/responses/openai_mcp_tool_resolve/mod.rs # docs/filters/openai_mcp_tool_resolve.md
leseb
left a comment
There was a problem hiding this comment.
P1 — Deferred discovery is broken for streaming requests.
collect_streaming_output_items:531 never routes tool_search_call into state.tool_search_calls. It falls through as ordinary output, so stream: true exits without running discovery. Add the streaming branch and an end-to-end streaming test.
P1 — Generated mcp_list_tools items violate the OpenAI schema.
mcp_list_tools_item:2052 exposes raw MCP tools containing inputSchema; Responses requires input_schema:42614. Every successful deferred listing is therefore nonconformant and may fail typed SDK parsing.
P1 — Streaming discovery failures bypass the canonical terminal lifecycle.
Deferred discovery calls resolve_error_rejection:293, producing a non-200 SSE error with sequence zero. It does not emit response.mcp_list_tools.failed followed by response.failed, nor persist the failed response object. The later-round path needs the same header-phase finalization used by the initial resolver.
P2 — Non-completed searches can cause network side effects.
The buffered collector queues every tool_search_call:514, including in_progress and incomplete. Only status: completed should trigger tools/list, matching function-call handling.
P2 — Connector discovery is serialized.
prepare_deferred_listings:1903 awaits each server sequentially. Total latency becomes the sum of all server timeouts and can exhaust the iterative-router budget. Independent listings should run concurrently while retaining transactional commit semantics.
P2 — Sanitization silently removes safe, meaningful fields.
The allowlist at sanitize_deferred_connector_tool:1499 drops standard fields such as server_description and allowed_callers. server_description specifically gives the model context needed to choose whether to search.
P2 — Required inference fixture coverage is absent.
This changes backend-facing inference transformation behavior but does not update coverage.yaml or add controlled provider evidence, as required by this repository’s AGENTS.md.
Streaming tool_search_call items never triggered tools/list, and successful listings plus streaming failures were nonconformant. Queue completed searches, emit input_schema, use the canonical SSE failure lifecycle, and cover the first-round sanitization with an inference fixture. Signed-off-by: mkoushni <mkoushni@redhat.com>
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com>
Keep deferred MCP coverage alongside main's chat-tool-echo feature in the committed inventory counts. Signed-off-by: mkoushni <mkoushni@redhat.com>
|
P1 — Streaming discovery. collect_streaming_output_items now queues completed tool_search_call items the same way the buffered collector does. Covered by unit tests plus streamed_deferred_connector_loads_on_tool_search. P1 — Listing schema. Public mcp_list_tools items map MCP inputSchema to Responses input_schema (plus name / description / annotations). P1 — Streaming failure lifecycle. Deferred listing failures use resolve_error_action during the body pre-read. openai_mcp_dispatch::on_request then emits HTTP 200 SSE with response.mcp_list_tools.failed and response.failed, matching the initial resolver. P2 — Completed-only searches. Only status: "completed" queues tools/list. In-progress / incomplete items stay in accumulated output and do not trigger network calls. P2 — Concurrent listings. Independent connectors are listed with try_join_all. Commit is still all-or-nothing: any listing or collision/size failure restores deferred_mcp. P2 — Sanitization. server_description and allowed_callers are kept. Credentials, URLs, connector_id, and headers are still stripped. P2 — Inference fixtures. Added responses.agentic.deferred_mcp_connectors with a synthetic first-round sanitization recording (defer_loading so replay never calls tools/list). check-inference and make test-inference-fixtures both pass. |
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com> # Conflicts: # apis/src/openai/responses/openai_mcp_tool_resolve/mod.rs
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com>
|
@mkoushni please rebase |
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com> # Conflicts: # apis/src/openai/responses/mcp_dispatch/mod.rs # apis/src/openai/responses/openai_mcp_tool_resolve/tests.rs # apis/src/openai/responses/state.rs # tests/integration/fixtures/inference/README.md # tests/integration/tests/suite/examples/openai_agentic_loop.rs # tests/utils/src/inference_fixture/coverage.rs
Keep deferred MCP coverage alongside main's streaming web_search fixture. Signed-off-by: mkoushni <mkoushni@redhat.com>
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com>
Keep deferred MCP discovery on the batched tool-call dispatch path from praxis-proxy#1027. Signed-off-by: mkoushni <mkoushni@redhat.com>
After praxis-proxy#1027 the agentic loop no longer forces parallel_tool_calls=false, so the first-round upstream body keeps string input. Point the file-url SSRF example at TEST-NET instead of live cloud IMDS. Signed-off-by: mkoushni <mkoushni@redhat.com>
Signed-off-by: mkoushni <mkoushni@redhat.com>
leseb
left a comment
There was a problem hiding this comment.
Blocker: a round mixing tool_search_call with a client-owned function call bypasses the mixed-ownership guard and sends the unresolved client call back to inference. See agentic_loop/mod.rs:591.
Streaming correctness: successful locally generated mcp_list_tools items appear only in the final snapshot; their incremental added/in_progress/completed/done events are never emitted. See openai_mcp_tool_resolve/mod.rs:2040.
Small harness hardening: replay now unconditionally considers openai_mcp_tool_resolve safe, which could permit eager tools/list calls in future fixtures. See replay.rs:431.
Signed-off-by: mkoushni <mkoushni@redhat.com>
Hosted tool_search_call mixed with a client function_call must fail before deferred discovery can send the unresolved client call back to inference. Successful mcp_list_tools items now record stream-events provenance so their incremental lifecycle is synthesized, and replay rejects eager MCP tools/list even when the connector URL is replay-owned. Signed-off-by: mkoushni <mkoushni@redhat.com>
|
Blocker — mixed ownership. Hosted tool_search_call now counts as server-owned in has_mixed_function_call_ownership. A completed search mixed with a client function_call is rejected with 502 before deferred discovery can send the unresolved client call back to inference. A search by itself still loops. Streaming — mcp_list_tools lifecycle. Successful deferred listings are recorded in locally_executed_output_items, and openai_stream_events treats mcp_list_tools as a local item. Clients now get added → mcp_list_tools.in_progress → completed → done, not only the final snapshot. Harness — replay gating. openai_mcp_tool_resolve is no longer on the unconditional allowlist. Replay still admits it for deferred sanitization, but any scenario request with an MCP tool that would eager-list (defer_loading missing or false) fails with scenario MCP listing is not replay-contained. |
leseb
left a comment
There was a problem hiding this comment.
almost there;
-
A completed tool_search_call with execution: "client" is still queued for server-side connector discovery. It should be returned to the caller without tools/list or another inference round. See agentic_loop/mod.rs:595.
-
Deferred discovery bypasses max_tool_calls. Even with the response-wide built-in-tool budget exhausted, it performs tools/list and continues inference. See openai_mcp_tool_resolve/mod.rs:1935.
Signed-off-by: mkoushni <mkoushni@redhat.com>
Return client-executed tool_search_call items to the caller without tools/list, and gate deferred connector discovery on the shared built-in tool budget so an exhausted max_tool_calls cap cannot list or continue inference. Signed-off-by: mkoushni <mkoushni@redhat.com>
|
Client-owned tool_search_call: a completed search with execution: "client" is still returned to the caller (accumulated output + persisted messages), but it is no longer queued for connector discovery. The loop exits without tools/list or another inference round. Hosted completed searches still queue and loop as before. max_tool_calls: deferred discovery now shares the response-wide built-in budget. If that budget is already exhausted — including when an earlier call in the same round took the last slot — has_pending_deferred_discovery is false, discover_deferred_connectors does not call tools/list, and both the agentic loop and MCP dispatch return done instead of continuing inference. |
leseb
left a comment
There was a problem hiding this comment.
Please fix the CI failures, also as a follow up please do those two small correctness cleanups: over-budget searches remain completed, and deferred listings omit error: null.
Thanks!
The deferred MCP fixture was recorded but the snapshot test and generated README still expected 24 features, failing lint, test, and coverage CI. Ratchet those to 25/25/30. Mark hosted tool_search_call items incomplete when max_tool_calls is exhausted, and omit error:null from successful mcp_list_tools listings. Signed-off-by: mkoushni <mkoushni@redhat.com>
Head branch was pushed to by a user without write access
Keep deferred MCP discovery on the praxis-proxy#1046 agentic-loop owner: hosted tool_search_call items loop only when connectors remain to list, and over-budget searches surface as incomplete. Signed-off-by: mkoushni <mkoushni@redhat.com>
The OpenResponses CLI launches supported templates concurrently, and IRR's hardcoded 30s idle timeout drops a starved SSE stream on Qwen CPU. Run streaming-response alone first, and regenerate stale mcp_dispatch docs. Signed-off-by: mkoushni <mkoushni@redhat.com>
|
@mkoushni you have conflicts |
…-connectors Signed-off-by: mkoushni <mkoushni@redhat.com> # Conflicts: # apis/src/openai/responses/openai_mcp_tool_resolve/mod.rs
Head branch was pushed to by a user without write access
cargo audit fails on RUSTSEC-2026-0285; 0.23.45 rejects TLS 1.3 handshake messages sent at the wrong encryption level. Signed-off-by: mkoushni <mkoushni@redhat.com>
Summary
Praxis
connector_idvalues are pipeline-local. Acceptingdefer_loading: truewithout an ownership model would have forwarded those IDs to the inference backend.openai_mcp_tool_resolveaccepts deferred configured connectors when the request includestool_search, injects the configured URL in-memory only, skips eagertools/list, and rewrites the backend body to a sanitizedtype: mcpstub (noconnector_id, URL, authorization, or headers). Sanitization keepsserver_descriptionandallowed_callers.openai_agentic_loopextracts completedtool_search_callitems into request state on both buffered and streaming rounds (not backendmessages). Incomplete searches stay in accumulated output and do not trigger listing.openai_mcp_dispatchloads pending deferred connectors ontool_search_call, lists them concurrently, then dispatches through the existingtools/callpath. Successful listings emit Responsesinput_schema(not MCPinputSchema).max_rewritten_body_byteschecks before mutatingmcp_tool_mapor output. Failures restore pending connectors.response.mcp_list_tools.failedthenresponse.failed) instead of a non-200 error body.connector_id+server_urlstill 400 before outbound activity. Eager connectors and directserver_urlare unchanged.The first inference round still sends the sanitized
type: mcpstub plustool_search; the backend must accept those OpenAI hosted-tool shapes. Any completedtool_search_callloads every pending deferred connector.Related issue
Closes #703
Validation
cargo test -p praxis-ai-apis --lib -- openai_mcp_tool_resolve agentic_loop mcp_dispatchcargo test -p praxis-tests-integration --test suite -- deferred_connector mixed_eager streamed_deferred_connectorcargo xtask check-inferenceandmake test-inference-fixturescargo clippy -p praxis-ai-apis --all-targets -- -D warningsmake lintChecklist
Signed-off-bytrailer.Breaking changes
None. Deferred
connector_idwas previously rejected; it is now accepted only withtool_searchand an agentic MCP dispatch loop.