Commit graph

171 commits

Author SHA1 Message Date
Mateo Wang
3b6e56716a
Merge pull request #36978 from Scott-Wilson-ZocDoc/fix/mcp-guardrail-usage-monitor
fix(guardrails): record MCP tool guardrail evaluations and blocks in …
2026-08-17 13:18:03 -07:00
yucheng-berri
f9f5c03884
fix(mcp): drop caller host and configured upstream headers from logged metadata (#36901)
* fix(mcp): drop caller host and configured upstream headers from logged metadata

The synthetic request that carries MCP client headers into
add_litellm_data_to_request forwarded the caller's Host header, and
Request.url is built from it, so a caller chose the proxy_server_request
url and the metadata endpoint that every logging callback records.

_upstream_credential_headers also only knew the configured client side
auth header and the x-mcp- prefix family, so a header name declared in
mcp_servers.<name>.extra_headers reached logging metadata in cleartext.
Those names are admin chosen, so no prefix rule can recognize them; read
them off the server registry instead. The header is still forwarded
upstream, which is what extra_headers is for. authorization is left out
because clean_headers already strips it and claiming it here would move
authenticated_with_header on the oauth passthrough config.

The Responses bridge tests stub the server manager, so their fakes gain
the registry accessor the sanitizer now reads.

* fix(mcp): drop caller host from the sanitized header mapping too

The synthetic request stopped forwarding host, but the parallel sanitizer
did not, so a forged hostname still reached the guardrail payload and the
list_tools spend row. Drop it there as well.

Exempt the configured identity headers from the upstream credential set.
get_user_from_headers resolves end user attribution off the same request
this module reconstructs, and it only fills end_user_id when auth left it
unset, so claiming user_header_name or a user_header_mappings name would
lose attribution on the MCP paths that authenticate upstream.

Drop the isinstance guard on extra_headers entries: the field is typed
list[str], so the check is dead and basedpyright scores it.

* fix(mcp): accept a bare user_header_mappings entry when exempting identity headers

get_internal_user_header_from_mapping and get_customer_user_header_from_mapping
both normalize a single mapping to a one element list, and config_settings.md
documents the key as a dict. Iterating the bare form yields its keys instead,
so the exemption silently matched nothing and an identity header also named in
an MCP server's extra_headers was dropped after all.
2026-08-14 17:21:07 -07:00
Scott Wilson
9858d021ee fix(guardrails): record MCP tool guardrail evaluations and blocks in usage monitor
MCP tool calls run their guardrails against a throwaway LLM-shaped dict
built by `ProxyLogging._convert_mcp_to_llm_format`, not against the dict
the tool call is logged from. `@log_guardrail_information` therefore
appended `standard_logging_guardrail_information` to that throwaway
dict's metadata bucket, where `get_standard_logging_object_payload`
never saw it, so the Guardrails Monitor reported zero evaluations and
zero blocks for all MCP traffic.

Thread the request's `litellm_logging_obj` into `pre_call_tool_check`
and `_create_during_hook_task` and bridge the guardrail records onto it:

- Seed `data["litellm_logging_obj"]`, which unified guardrails read and
  pass into `apply_guardrail`.
- Call `_sync_guardrail_info_to_logging_obj` in a `finally`, which is
  what native guardrails need and what makes the block path work: a
  blocked call raises straight out of `pre_call_tool_check`, so the
  record has to be attached before the exception leaves the frame.

Only the guardrail evaluation records are copied. The synthetic
request's messages and tool arguments are deliberately left behind --
they can carry end-user data and nothing in the monitor needs them.

In `call_mcp_tool`, flush the failure handlers before
`post_call_failure_hook` so the `status="failure"` standard logging
object exists when `_ProxyDBLogger.async_post_call_failure_hook` writes
the spend-log row the monitor's "Total Blocked" counts. Both handlers
gate on `should_run_logging("sync_failure")` / `("async_failure")` and
then mark it, so the `@client` wrapper's own post-raise logging is a
no-op and nothing is double-counted -- the same pattern
`_fire_mcp_tool_call_logging` already uses for `isError=True`.

Threaded through every MCP tool entry point: the managed-server path,
the local-OpenAPI registry path, the legacy registry fallback, and the
Responses API's `_execute_tool_calls`.
2026-08-14 17:52:36 -04:00
devin-ai-integration[bot]
59eeae374c
fix(mcp): expose client HTTP headers to logging callbacks and hooks (#36724)
* fix(mcp): expose client HTTP headers to logging callbacks and hooks

MCP protocol tool calls built a synthetic Request with only content-type, so metadata.headers reaching logging callbacks and guardrails was empty while /mcp-rest/tools/call exposed the full set. Rebuild the synthetic request from the connection's raw headers (shared with the sampling path), and pass sanitized headers to the pre-call hook, the MCP to LLM guardrail bridge and the Responses API MCP bridge. Credential headers stay masked and proxy key headers stripped.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp): strip custom proxy key and upstream MCP credential headers from logging copies

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* refactor(mcp): make client side auth header name accessor public

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp): strip custom proxy key and client redaction opt-out from mcp headers

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(mcp): drop custom proxy key header in the synthetic request builder

Strips general_settings.litellm_key_header_name in build_synthetic_mcp_request so every caller, including sampling, is covered, and reverts passing general_settings into add_litellm_data_to_request on the tool call path since that also switches on enforced_params.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: shivam <shivam@berri.ai>
2026-08-13 20:07:16 -07:00
Emerson Gomes
4ea5749642
feat(azure-ai): add Grok 4.3 model metadata (#27932)
* Add Azure AI Grok 4.3 metadata

* Address Azure Grok 4.3 test feedback

* Drop empty tool choice in responses bridge

* style(azure-ai): update Grok metadata tests
2026-08-13 17:25:17 -07:00
yuneng-jiang
16ce5031f0
Merge branch 'litellm_internal_staging' into litellm_/remove-no-guard-mirror-tests 2026-08-12 10:38:24 -07:00
Mateo Wang
cfcd0cda8a fix(responses): leave namespace unset on non-namespace tool calls 2026-08-12 00:28:15 -07:00
Mateo Wang
a64a83bf36 fix(responses): keep custom_tool_call echoes on their advertised short name 2026-08-12 00:19:08 -07:00
Mateo Wang
ca14e52b08 fix(responses): requalify echoed namespace tool calls with their flattened name 2026-08-12 00:01:34 -07:00
Mateo Wang
397fcd0e6b fix(responses): serialize flattened namespace tools and keep tool results adjacent to tool_calls 2026-08-11 23:41:44 -07:00
Mateo Wang
ae2a1f4aba Merge branch 'litellm_internal_staging' into litellm_fix_codex_responses_namespace_tools 2026-08-11 23:16:34 -07:00
mateo-berri
6bce073520 fix(responses): keep chat-shaped usage extras from colliding in the bridge
Gemini image usage carries prompt_tokens and friends as extra fields on
ResponseAPIUsage, which collided with the bridge's explicit kwargs and
raised TypeError. Exclude keys the bridge already sets explicitly.
2026-08-11 18:34:27 -07:00
mateo-berri
52f9b4a6e1 fix(xai): keep Responses API usage schema while billing web search
Drop the transform overrides that swapped response.usage to the chat
shape, which broke the /v1/responses client contract. Provider extras
like server_side_tool_usage_details already survive validation via
ResponseAPIUsage extra fields, so the shared usage bridge now carries
them onto the bridged chat Usage generically. The web_search_call
output gate also reads dict output items, since items that fail SDK
validation stay plain dicts, and the chat path gains billing tests.
2026-08-11 17:16:43 -07:00
Yang Yang
749a8b0701 fix(xai): keep chat Usage through Responses completions bridge
xAI already converts Responses usage to chat Usage so web_search_calls survive
cost tracking. The chat completions bridge then re-ran the Responses usage
transform and crashed on missing input_tokens. Pass through already-chat Usage
and chat-shaped dumps instead
2026-08-08 16:49:09 -07:00
Yang Yang
3aea951e6c refactor(xai): keep Responses tool usage pass-through in llms/xai
Revert shared responses/utils.py extras forwarding. Attach
server_side_tool_usage_details on chat Usage inside XAIResponsesAPIConfig
so cost calc keeps web_search_calls without provider logic in shared utils.
2026-08-08 16:21:06 -07:00
Yang Yang
014d59f4c4 refactor(responses): pass through extra usage fields generically
Avoid hard-coding provider-specific usage keys in shared Responses
utilities; forward any non-standard usage attributes onto chat Usage
for provider cost tracking (e.g. server_side_tool_usage_details).
2026-08-08 16:20:37 -07:00
devin-ai-integration[bot]
f05d468769
fix(responses): forward allowed_openai_params through the chat completions bridge (#35885)
Resolves #35878

Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-08-07 19:57:26 -07:00
Daniel Cadenas
08d4a39f23
fix(responses): harden namespace tool mapping 2026-08-06 21:25:57 -03:00
Daniel Cadenas
bd1478e4ad
fix(responses): preserve Codex namespace tool calls 2026-08-06 20:53:05 -03:00
mateo-berri
7288247682 Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_fix_openai_cache_token_details_loss
# Conflicts:
#	litellm/litellm_core_utils/llm_cost_calc/utils.py
#	litellm/litellm_core_utils/streaming_chunk_builder_utils.py
#	litellm/litellm_core_utils/streaming_handler.py
#	tests/test_litellm/litellm_core_utils/test_streaming_chunk_builder_utils.py
2026-08-05 01:23:54 -07:00
mateo-berri
9eeff06263 Merge origin/litellm_internal_staging into litellm_lit4395_cursor_agent 2026-08-04 10:20:03 -07:00
Mateo Wang
f60e99c583
Merge pull request #34531 from BerriAI/litellm_forward_client_headers_responses_api
fix(responses): forward client headers to the provider on /v1/responses
2026-08-03 20:02:21 -07:00
Tin Chi Lo
b79b01e38a fix(proxy): translate custom tool grammar formats and tool_choice across API surfaces
Cursor's ApplyPatch is a grammar-constrained custom tool; the Responses
surface carries the grammar flat while chat completions wraps the same
fields in a grammar object, so the nested envelope from the previous
commit still 400d at OpenAI (tools[N].custom.format.grammar). Adds a
shared flat to nested format helper pair in prompt_templates/common_utils
used by the cursor messages arm and the chat-to-responses bridge, nests
flat Responses-style tool_choice objects on the cursor arm, flattens chat
custom tool_choice on the chat-to-responses bridge, and maps custom
tool_choice to function tool_choice on the responses-to-chat bridge to
match that bridge's custom-to-function tool downgrade
2026-08-01 11:26:09 -07:00
mateo-berri
43fad507de fix(responses): map all documented in-stream error codes to real HTTP statuses 2026-07-30 19:17:32 -07:00
Devin AI
c429a0e4a7 fix(cost_tracking): keep OpenAI prompt cache token details through usage reassembly 2026-07-27 18:36:26 +00:00
Yuneng Jiang
e4a0475263
test: remove four mirror test files that exercise none of their module
A second mutation batch scored the previously unmapped mirror files on
current staging. These four generate mutants for the module they are
named after, yet no test in the file executes any of them; their
test-context coverage lands on generic shared machinery or, for the
guardrail translation handler remainder, on no litellm line at all.
Eight sibling findings that do exercise a different real module are
kept for retargeting instead of removal.
2026-07-25 10:57:19 -07:00
mateo-berri
6ff88ba5e9 fix(responses): strip include_usage from stream_options instead of dropping the param 2026-07-24 16:46:49 -07:00
shivam
a376f72400 fix(responses): stop treating stream_options as a Responses API param
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-24 20:53:05 +00:00
milan
7716e47519 fix(responses): compare forwarded header names case-insensitively
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-24 20:16:25 +00:00
milan
fb9d30224b fix(responses): forward proxy client headers to the provider
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-24 19:04:22 +00:00
Krrish Dholakia
2f502a1bfc fix(cost_tracking): map cache_write_tokens on Responses API usage path
The Responses API (/v1/responses) usage transform rebuilt prompt token
details and dropped OpenAI's input_tokens_details.cache_write_tokens, so
gpt-5.6 cache-creation tokens were never logged or billed via that route.
Map it in the transform, and make PromptTokensDetailsWrapper keep
cache_write_tokens and cache_creation_tokens in sync on assignment.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-23 19:07:08 -07:00
Shivam Rawat
abf18f8760
Merge pull request #33422 from BerriAI/litellm_fix_responses_reasoning_items
fix(responses): preserve reasoning through prompt hooks
2026-07-22 19:42:09 -07:00
mateo-berri
354f3971a9 fix(bedrock_mantle): gate unsupported service_tier on drop_params for the Responses API 2026-07-20 19:49:42 -07:00
tin-berri
43e4af73f0
Merge pull request #33631 from BerriAI/litellm_lit4517_messages_mcp_gateway
feat(mcp): support MCP servers on the Anthropic /v1/messages API
2026-07-20 16:22:03 -07:00
devin-ai-integration[bot]
7891388975
feat(rust): 1:1 port of OpenAI Responses API WebSockets to litellm-rust (#33849)
* feat(rust): add OpenAI Responses WebSocket gateway

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* test(rust): cover Responses WebSocket gateway behavior

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* fix(rust): align Responses WebSocket parity

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* feat(rust): expose Responses WebSockets through bridge

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* fix(rust): reject non-openai responses deployments early

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* fix(rust): align Responses WebSocket bridge semantics

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* refactor(rust): move Responses instrumentation into core

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* fix(rust): preserve Responses WebSocket callback dispatch

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

* build(deps): authorize vcrpy and locust licenses in liccheck

Co-Authored-By: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
2026-07-19 01:55:35 +00:00
Tin Chi Lo
98bf25e8af Merge origin/litellm_internal_staging into litellm_mcp_aggregate_outcomes
Append-append conflict at the end of test_mcp_server.py between this branch's aggregate-outcome
tests and the mode-aware preemptive-401 tests from staging; both kept
2026-07-17 10:19:36 -07:00
Tin Chi Lo
ae952ce971 feat(mcp): support MCP servers on the Anthropic /v1/messages API
MCP tool calling worked on /v1/chat/completions and /v1/responses but not on
/v1/messages. Those are the only two surfaces with an MCP gateway entry point,
so a litellm_proxy MCP reference reached Anthropic verbatim inside tools and the
API rejected the request with "Input tag 'mcp' found using 'type' does not match
any of the expected tags". The playground never surfaced this because it dropped
the reference before sending, and disabled the MCP selector for the endpoint.

Add the third entry point in anthropic_messages_handler, ahead of the provider
branch so it covers the native path and both bridges from one place. The gateway
expands the reference against the caller's own credentials and access control,
which is the whole point of routing it through litellm rather than handing the
url to the provider.

/v1/messages needs Anthropic's own tool shape, so transform_mcp_tool_to_anthropic_tool
joins the OpenAI chat and Responses transforms alongside it. The tool loop speaks
tool_use and tool_result rather than OpenAI tool_calls, and reuses the existing
FakeAnthropicMessagesStreamIterator to re-stream the result, the same pattern the
websearch interception already uses on this route. Argument extraction moves into
the shared extractor: an Anthropic tool_use block carries its arguments under
`input`, and reading only `arguments` failed silently, executing the tool with
every argument dropped.

On the frontend the request builder declared selectedMCPTools and never read it,
so no tools key was ever sent. Wire it through a shared block builder and add the
endpoint to MCP_SUPPORTED_ENDPOINTS, which is what greys the selector out.

Resolves LIT-4517
Resolves LIT-4518
2026-07-16 18:35:58 -07:00
Krrish Dholakia
4db0bdf465 fix(responses): handle non-message-only prompt input
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-15 18:51:07 +00:00
Krrish Dholakia
4baee71bdd chore(responses): minimize regression test diff
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-15 18:41:02 +00:00
Krrish Dholakia
ebb0f7e4cf fix(responses): preserve reasoning through prompt hooks
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-07-15 18:40:32 +00:00
mateo-berri
df60e36d07 fix(responses): end stream cleanly on transport error after terminal event 2026-07-15 08:33:50 -07:00
Tin Chi Lo
f776ea7f9b feat(mcp): per-server outcomes for aggregate tools/list and truthful single-server REST statuses
The aggregate MCP tools/list absorbed every per-server failure (upstream 401/403/5xx, timeouts,
network errors) into that server contributing zero tools, making a broken upstream indistinguishable
from a healthy server with no tools; the single-server REST list masked the same failures as
{"tools": [], "error": null, "message": "Successfully retrieved tools"}

Phase 2 of the MCP error-handling framework (LIT-4419): the manager fetch hops now raise a
classified MCPServerListError (faults/list_outcomes.py: total classifier, frozen outcome values)
instead of returning [], and each boundary applies the relay-vs-absorb policy matrix. The aggregate
keeps serving the healthy subset but records each server's outcome, surfaced on the tools/list
result _meta under litellm.ai/server_outcomes (the SDK passes a ListToolsResult through unwrapped)
and in spend logs as per_server_list_outcomes. Single-server REST requests relay truthful statuses
(unreachable/upstream_error 502, timeout 504, internal 500) and access denials now surface as real
403s instead of 200 unexpected_error bodies; upstream 403s surface through MCPUpstreamAuthError
like 401s. Outcome wire values carry category and status code only, never upstream prose

Resolves LIT-4421
2026-07-14 20:03:56 -07:00
Thibault Serot
3a2d14e1a6 fix(responses): continue MCP gateway tool turns from the final response and surface failures
When a /responses request uses a hosted MCP tool (server_url: litellm_proxy/<label>)
with store=true and the model calls a tool, the gateway auto-executes the tool and
streams one logical response stitched from several upstream responses: an interim
response whose only output is the function_call, then the post-tool answer

B1 (correctness): every streamed event was pinned to the first round's response id,
i.e. the interim response that carries the function_call but no tool output. The
client then continued the next turn from that dangling response and the provider
rejected it with "No tool output found for function call <id>", which on the
streaming path surfaced as a silent empty completion. The fix adopts each
auto-execute round's own response id (the cached id is reset when a follow-up round
starts) so the client continues from the final round, whose stored input chain
includes the function_call_output

B2 (robustness): initial and follow-up call failures were swallowed; the stream
emitted the mcp_list_tools discovery events and then closed with HTTP 200 and no
output and no error. The fix stashes the failure, makes the initial call eagerly in
aresponses_api_with_mcp so a pre-stream failure re-raises as a real 4xx before any
SSE bytes are written, and emits a terminal error event when a follow-up call fails
mid-stream

Adds regression tests covering continuation exposing the final round's response id
rather than the interim tool-call id, a follow-up failure emitting a terminal error
event, and an initial-call failure being stashed for eager re-raise
2026-07-13 11:33:02 +10:00
Mateo Wang
249a999506
fix(responses-api): raise APIError on in-stream error events; widen ErrorEventError.param to accept dict (#32835)
* fix(responses-api): raise APIError on in-stream error events; widen ErrorEventError.param

- BaseResponsesAPIStreamingIterator._maybe_raise_for_error_event inspects each
  chunk and raises litellm.APIError for type=error and type=response.failed events
  so callers see an exception instead of a benign stream chunk
- rate_limit* codes map to 429; client error codes (invalid_request_error,
  context_length_exceeded, etc.) map to 400; all other codes default to 500;
  raw integer codes are never used as-is as HTTP status codes
- ErrorEventError.param widened from Optional[str] to Optional[Union[str, Dict]]
  to prevent Pydantic ValidationError on dict-typed param payloads silently
  dropping error events before any type inspection

* test(responses-api): add streaming iterator error event tests to CI-covered path

* test(responses-api): cover response.failed, dict-error, null-error, and sync iterator paths

* test(responses-api): set completion_start_time on mock logging objects for internal staging _process_chunk

* fix(responses-api): map insufficient_quota to 429, derive failed-response log status from error code, and record failed-stream usage for spend accounting

insufficient_quota moves out of the 400 bucket; OpenAI returns HTTP 429 for it and the non-streaming exception mapping treats 429 as RateLimitError, so the in-stream mapping now agrees

_handle_logging_failed_response previously hardcoded APIError(status_code=500), so a rate-limited response.failed was logged to integrations as 500 while the caller saw 429; it now shares the same error-code-to-status mapping via _error_event_fields and _status_code_for_error_code

usage carried on a response.failed event is now stashed as combined_usage_object with its computed cost on the logging object before failure handlers run, reusing the mid-stream-interruption spend recovery path (_failure_handler_helper_fn, proxy post_call_failure_hook, _ProxyDBLogger), so failed streams count their billed tokens instead of logging zero cost

dedupe: TestMaybeRaiseForErrorEvent in tests/llm_responses_api_testing duplicated tests/test_litellm/responses/test_streaming_iterator_error_events.py, which is the canonical mirrored location and CI-covered via test-unit-responses-caching-types; the duplicate class is removed

* fix(responses-api): wrap retriable in-stream errors in MidStreamFallbackError and map error type field to status

Mirror chat streaming semantics from _handle_stream_fallback_error: 429 and
5xx in-stream error events now raise MidStreamFallbackError carrying the
mapped APIError so the router's FallbackResponsesStreamWrapper triggers
mid-stream fallback and cooldown; non-retriable 4xx still raise APIError
directly. Status mapping now reads both the OpenAI error type and code
fields, so type-classified client errors (e.g. invalid_request_error with
code invalid_prompt) map to 400 instead of falling through to 500.

* fix(responses-api): accumulate streamed output text so mid-stream fallback continues instead of restarting

MidStreamFallbackError was always raised with generated_content="", so the
router's stream_with_fallbacks treated every mid-stream error as pre-first-chunk
and retried with the original input, streaming duplicated content to clients
that had already received partial output. The iterators now accumulate
response.output_text.delta text (mirroring chat's response_uptil_now) and pass
it as generated_content, letting the router build a continuation input via
_build_responses_continuation_input.

* test(responses-api): pin in-stream token limit error to raised APIError

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-07-10 20:25:38 -07:00
Mateo Wang
ee6e8077ae
fix(responses): preserve reasoning_tokens through chat->responses usage translation (#32837)
* fix(responses): preserve reasoning_tokens through chat->responses usage translation

Remove the unconditional else-branch that wrote reasoning_tokens=0 whenever
completion_tokens_details.reasoning_tokens was None or absent. Also change
OutputTokensDetails.reasoning_tokens from int=0 to Optional[int]=None so that
re-instantiation without explicit reasoning_tokens no longer silently zeroes out
the field, and remove the same hardcoded zero from the mock_responses_api_response
initializer.

* test(responses): update assertions to match Optional[int] reasoning_tokens default

* fix(responses): preserve explicit reasoning_tokens=0 in usage translation

Align the reasoning_tokens guard with the is-not-None guards used for
text_tokens and image_tokens: a provider-reported zero passes through
while an absent value stays omitted.

---------

Co-authored-by: Deepanshu <deepanshu.lulla@alpha-sense.com>
2026-07-10 20:15:49 -07:00
Mateo Wang
c2d8a17692
test(responses): replace perma-skip azure shell e2e with offline coverage (#32444) 2026-07-08 10:01:41 -07:00
thibault-linktree
4b0ac8b352
fix(responses): make response-id encoding idempotent to prevent MCP gateway double-encoding previous_response_id (#32034) 2026-07-07 19:47:11 -07:00
Yassin Kortam
765fd0762e
fix(responses): stop scheduling sync success_handler concurrently with async_success_handler (#32239) 2026-07-07 09:13:50 -07:00
Yassin Kortam
db133d4bc4
fix(mcp): defer proxy import so completion(tools=...) works without proxy extras (#32339) 2026-07-07 18:57:24 +03:00
Sameer Kankute
a78dc69a09
fix(mcp): alias/display-name tool routing, REST filters, BYOK auth (#32320)
* fix(mcp): resolve tool name prefix via known server prefixes, not string match

When an MCP server's alias differs from its server_name, tool names are
listed with the alias prefix but _execute_tool_calls compared that prefix
against the server_name stored in tool_server_map. The mismatch silently
skipped prefix stripping, forwarding the fully-prefixed tool name upstream
and causing "Unknown tool" failures. Resolve the actual MCPServer object
and strip using its known prefix forms (alias, server_name, server_id)
instead.

* fix(mcp): preserve tool overrides and scope REST tool listing

Return saved tool display/description overrides from the server table API
so the edit UI reloads them, resolve display names before prefix stripping
on tool calls, and honor mcp_server_name and toolset_name filters on the
REST tools list endpoint.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(mcp): inject BYOK credentials on Playground OpenAPI tool calls

Playground and Responses API route MCP execution through call_tool, which
skipped BYOK lookup and never set the OpenAPI auth ContextVar, so upstream
calls went out unauthenticated despite a stored user credential.

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(mcp): cover alias-mismatch prefix stripping and display-name reverse mapping

Regression tests for _execute_tool_calls: an MCP server whose alias differs
from its server_name must still have its tool-name prefix stripped correctly,
and a tool called by its configured display name must resolve back to the
original tool name before dispatch.

* fix(mcp): validate tool display names against Bedrock's tool-name pattern

A display name replaces the tool name sent to the LLM provider, so a value
with spaces or other special characters saves successfully but fails every
subsequent Bedrock tool call. Validate tool_name_to_display_name server-side
(create/update payload) against Bedrock's [a-zA-Z0-9_-]+ constraint, and add
matching inline validation plus a save-blocking guard in the Admin UI's
create and edit MCP server forms.

* style(mcp): fix ruff/prettier formatting on CI

No logic changes; satisfies the format checks flagged on PR #32320.

* fix(mcp): fix CI failures on PR - complexity budget and stale test mock

Extract toolset-scope resolution and query-param normalization out of
list_tool_rest_api into helpers to bring it back under the C901 complexity
budget (was 18, now within the 15 threshold).

Add the missing get_mcp_server_by_name stub to the streaming iterator test's
mock manager; the alias-fallback resolution added for tool-name-prefix
stripping calls it unconditionally when _get_mcp_server_from_tool_name misses.

* test(mcp): cover BYOK OpenAPI auth-header helpers to close codecov patch gap

_format_byok_openapi_auth_header, _openapi_forwarded_extra_headers, and
_resolve_byok_mcp_auth_header were only exercised indirectly via a mocked
call_tool test, leaving their branches (auth-type formatting, header
forwarding/stripping, missing-credential 401) uncovered.

* fix(mcp): resolve BYOK auth before queuing the during-hook task

_resolve_byok_mcp_auth_header can raise a 401 when no credential is stored.
Resolving it after during_hook_task was already queued meant a hook's
side effects (audit logging, rate-limit bookkeeping) could run and record
success for a tool call that then fails on the missing credential.

* fix: correct mcp alias routing regressions

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-07 20:50:21 +05:30