The listed-tool metadata and per-caller catalog cache move to a follow-up PR
stacked on this one, and the RFC 9728 sign-in challenge and discovery metadata
move to a second PR stacked on that. On its own the guardrail evaluates the
tool name and arguments and the caller presents its Entra token
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
A forwarded x-api-key, api-key or apikey header is the upstream's own credential and leaves the caller's top-level Authorization with the gateway, so Agent 365 discovery and the connect-time challenge must not be suppressed for those servers. Only modes or forwarded headers that consume top-level Authorization opt a server out.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Forwarded client headers on bridged /v1/responses calls were serialized into the
OpenAI JSON body as extra_headers when EXPERIMENTAL_OPENAI_BASE_LLM_HTTP_HANDLER
was set, and OpenAI rejected the request with unknown_parameter. The headers are
already merged into the outgoing HTTP headers, so only set the SDK-style
optional param on the SDK client path
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Entra reports a forged or garbled On-Behalf-Of assertion as invalid_client, the
same OAuth error it uses for a bad gateway client secret. Read the AADSTS
error_codes so a malformed assertion sends the caller back to sign in at connect
and at the tool call instead of reporting the gateway's credentials as broken.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
get_litellm_params() keys such as model_alias_map, completion_call_id and
custom_prompt_dict were missing from all_litellm_params, so when a bridge
forwarded litellm_params as completion kwargs they were swept into extra_body
and OpenAI rejected the call with "Unknown parameter: 'model_alias_map'".
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Connect-time sign-in challenge now asks Entra to exchange the presented assertion instead of only checking
its compact-JWS shape, so expired, wrong-audience or forged bearers get the RFC 9728 challenge while gateway
credential and provider failures still surface on the tool call. Only default_on guardrails the caller has not
opted out of advertise or challenge, since anonymous metadata cannot see key-selected guardrails. Servers whose
Authorization is minted per caller by MCPJWTSigner list tools per caller instead of sharing one cache slot
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Key the listed-tool cache by every request input that changes the upstream catalog (caller identity, per-server auth header, forwarded headers, header-driven stdio env, relayed bearer) so one caller's tool metadata never reaches Agent 365 for another. Advertise Entra in anonymous protected-resource discovery only for default_on guardrails. Check the key's server grant before issuing the Agent 365 sign-in challenge so a key without access gets the grant's 403. Group conversationId by the MCP session id, falling back to the call id. Run Agent 365 in the parallel block-only phase so it judges the arguments the sequential guardrails hand upstream. Apply tool_name_to_description to local-registry tool metadata sent to Agent 365
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Prompt Security reports a queued sanitization job as status "created" before
it moves to "in progress" and "done". The poller treated anything other than
those two known strings as an error and returned HTTP 500 on the first poll, so
every image or file request through the guardrail failed while the vendor job was
still queued.
Only "done" is terminal now. Every other status is logged and polled again until
max_poll_attempts or the outer file_sanitization_timeout, after which the existing
fail-open or fail-closed (408) policy applies.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Releasing the slot unconditionally from the route raced the limiter's own
success handler on the logging worker: both could read the same stashed
acquisition before either cleared it, and under the integer in-memory
fallback that double-decrements the counter. The route now releases only on
exits without a success callback (pre-call rejection, pre-call cancellation,
and Phase 2 exits without the success stamp), matching the HTTP disconnect
path's ownership rule.
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>