Skip to content

feat(mcp): forward OCI headers exclusively to MCP tool calls via JSON-RPC _meta#1415

Closed
aravindh-raja wants to merge 5 commits into
lightseekorg:mainfrom
aravindh-raja:feature/file-search-tool-mcp-server-integration
Closed

feat(mcp): forward OCI headers exclusively to MCP tool calls via JSON-RPC _meta#1415
aravindh-raja wants to merge 5 commits into
lightseekorg:mainfrom
aravindh-raja:feature/file-search-tool-mcp-server-integration

Conversation

@aravindh-raja
Copy link
Copy Markdown

@aravindh-raja aravindh-raja commented Apr 29, 2026

Summary

MCP tool calls previously had no mechanism to receive per-request headers like OCI delegation tokens from the incoming gateway request. This change introduces header forwarding to MCP tool calls by embedding custom headers in the JSON-RPC _meta.extra_headers field of the tools/call request.

What changed

model_gateway/src/routers/common/header_utils.rs

  • Added extract_mcp_forward_headers() — extracts only MCP-specific headers (x-smg-oci-delegation-token, x-smg-oci-compartment-id) from incoming requests.
  • Refactored shared iteration logic into a private extract_headers_matching() helper used by both extract_forwardable_request_headers() and extract_mcp_forward_headers().
  • extract_forwardable_request_headers() no longer includes MCP-specific headers, keeping them out of LLM provider calls.

model_gateway/src/routers/openai/responses/non_streaming.rs
model_gateway/src/routers/openai/responses/streaming.rs

  • At MCP session creation, MCP-specific headers are extracted separately via extract_mcp_forward_headers() and merged into the forwarded headers map before passing to McpToolSession.

crates/mcp/src/core/orchestrator.rs

  • call_tool_on_peer() embeds forwarded headers in _meta.extra_headers of the JSON-RPC tools/call request using rmcp's send_request_with_option API.

crates/mcp/src/core/handler.rs / crates/mcp/src/core/session.rs

  • HandlerRequestContext and McpToolSession carry forwarded_headers through the call chain from the router layer to the orchestrator.

Why

OCI-backed MCP servers require per-request headers such as x-smg-oci-delegation-token and x-smg-oci-compartment-id to authorize tool execution on behalf of the caller. Without this change, these headers were not forwarded to MCP tool calls and the MCP server had no way to receive them.

These headers are specific to MCP tool calls and must not be sent to LLM providers (OpenAI, Anthropic, etc.), so they need a dedicated extraction path separate from the general header forwarding used for tracing, auth, and routing.

How

Headers flow through the system as follows:

  1. An incoming request arrives at the gateway with x-smg-oci-delegation-token and/or x-smg-oci-compartment-id headers.
  2. The response handler (streaming or non-streaming) calls extract_mcp_forward_headers() to extract only MCP-specific headers, and merges them with the general forwarded headers.
  3. The merged map is passed to McpToolSession::new_with_headers(), which stores it on the session.
  4. When a tool call is executed, the session creates an McpRequestContext carrying the forwarded_headers.
  5. The orchestrator's call_tool_on_peer() checks for non-empty forwarded headers and, when present, constructs a PeerRequestOptions with meta: Some(Meta({ "extra_headers": { ... } })).
  6. It calls peer.send_request_with_option() instead of peer.call_tool(), embedding the headers in the JSON-RPC _meta field of the tools/call request.
  7. The downstream MCP server reads the headers from _meta.extra_headers in the protocol message.

This approach keeps header forwarding at the JSON-RPC protocol level rather than the HTTP transport layer. Because the headers are part of the message payload, there is no shared mutable state, no race conditions, and concurrent tool calls to the same server execute without serialization.

Test plan

  • cargo test -p smg --lib header_utils — 23 unit tests pass, including:
    • test_extract_mcp_forward_headers — verifies only OCI headers are returned
    • test_extract_mcp_forward_headers_empty_when_none — verifies empty/None handling
    • test_extract_forwardable_excludes_mcp_only_headers — verifies OCI headers are excluded from general extraction
    • test_is_mcp_forward_header — verifies the MCP header filter
    • test_should_forward_request_header_blocked — verifies OCI headers are not in the general forwarding allowlist
  • cargo test -p smg-mcp — 201 unit tests pass, including test_session_preserves_forwarded_headers_in_request_context
  • Manual verification: MCP tool calls to an OCI-backed MCP server receive the delegation token in _meta.extra_headers
  • Manual verification: LLM provider calls (OpenAI, Anthropic, etc.) do not contain x-smg-oci-* headers

Verification

  1. Executed file search tool and ensured the request is successful
Screenshot 2026-04-29 at 6 00 23 PM
  1. Executed regular responses API call and ensured it was sucessful.

Summary by CodeRabbit

  • New Features

    • File Search is now supported as a built-in MCP tool
  • Enhancements

    • Tool execution now forwards request headers/tokens through approval and peer calls for richer request context
    • Header forwarding logic split into general and MCP-specific sets for clearer, safer propagation
    • File-search result parsing made more consistent and robust
  • Tests

    • Updated/expanded tests for header extraction and forwarding behavior

Signed-off-by: aravind <aravindh.raja@oracle.com>
Signed-off-by: aravind <aravindh.raja@oracle.com>
@github-actions github-actions Bot added mcp MCP related changes model-gateway Model gateway crate changes openai OpenAI router changes labels Apr 29, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 29, 2026

📝 Walkthrough

Walkthrough

HTTP headers are extracted into a new MCP-specific set and threaded through MCP orchestration so per-request forwarded headers are sent in peer tool calls. OpenAI handlers now pass MCP-only headers into MCP sessions. FileSearch is recognized as a hosted built-in and file-search parsing constants were introduced.

Changes

MCP Orchestration, Header Extraction & OpenAI handlers

Layer / File(s) Summary
Header extraction helper
model_gateway/src/routers/common/header_utils.rs
Introduces extract_matching_headers, adds is_mcp_forward_header, adds extract_mcp_forward_headers, and updates extract_forwardable_request_headers to use the shared helper. Repeated-header merging and UTF‑8 handling centralized.
Gateway usage / wiring
model_gateway/src/routers/openai/responses/non_streaming.rs, model_gateway/src/routers/openai/responses/streaming.rs
Streaming and non-streaming OpenAI handlers now call extract_mcp_forward_headers(...) and pass the returned headers into McpToolSession::new_with_headers.
Orchestrator: API surface and threading
crates/mcp/src/core/orchestrator.rs
execute_tool_with_reconnect and execute_tool_impl signatures extended to accept &HashMap<String,String> forwarded headers. Post-approval continuation and reconnect retry paths updated to pass request_ctx.forwarded_headers. Dynamic per-request tool execution also routes through the new header-aware call path.
Orchestrator: peer call implementation
crates/mcp/src/core/orchestrator.rs
execute_on_server refactored to delegate to call_tool_on_peer. If forwarded headers are empty, keeps existing peer.call_tool(request) path; otherwise embeds headers in JSON-RPC _meta.extra_headers and uses send_request_with_option, mapping transport/response errors to appropriate McpError variants.
Tests
model_gateway/src/routers/common/*_tests
Header extraction tests updated/extended to assert MCP-only headers are excluded from general forwardable set, included by extract_mcp_forward_headers, and that CSV-merge vs first-value semantics behave as expected.

FileSearch routing & parsing

Layer / File(s) Summary
Routing recognition
model_gateway/src/routers/common/mcp_utils.rs
collect_builtin_routing and extract_builtin_types extended to recognize OpenAI ResponseTool::FileSearch(_) as BuiltinToolType::FileSearch so it can be routed to MCP servers.
Transformer parsing constants
crates/mcp/src/transform/transformer.rs
Introduces deterministic KEY_* constants and updates to_file_search_call, extract_file_results, and parse_file_result to use these constants for file-search JSON parsing.

Sequence Diagram

sequenceDiagram
    participant Client as Client Request
    participant GW as Gateway (header_utils)
    participant Orch as MCP Orchestrator
    participant Server as MCP Server

    Client->>GW: HTTP request with headers
    GW->>GW: extract_mcp_forward_headers(headers)
    GW->>Orch: McpToolSession::new_with_headers(mcp_headers)
    Orch->>Orch: approval flow / execute_tool_with_reconnect(forwarded_headers)
    Orch->>Orch: execute_tool_impl(..., forwarded_headers)
    Orch->>Orch: call_tool_on_peer(request, forwarded_headers)
    alt forwarded_headers non-empty
        Orch->>Server: send_request_with_option(JSON-RPC with _meta.extra_headers)
    else forwarded_headers empty
        Orch->>Server: peer.call_tool(request)
    end
    Server-->>Orch: CallToolResult
    Orch-->>GW: Tool response
    GW-->>Client: Final response
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

tests

Suggested reviewers

  • CatherineSue
  • key4ng
  • zhaowenzi
  • slin1237

Poem

🐰 I hopped through headers, soft and fleet,
Tokens tucked in pockets neat,
MCP now carries what I bring,
FileSearch sings and servers spring,
A rabbit’s hop makes calls complete. 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: forwarding OCI headers to MCP tool calls via JSON-RPC _meta field.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

Review ran into problems

🔥 Problems

Timed out fetching pipeline failures after 30000ms

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

@aravindh-raja aravindh-raja changed the title feat(mcp): forward OCI headers exclusively to MCP tool calls via JSON-RPC _metaDon feat(mcp): forward OCI headers exclusively to MCP tool calls via JSON-RPC _meta Apr 29, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

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 implements header forwarding for MCP tool calls, specifically targeting OCI delegation tokens and compartment IDs. The changes include refactoring header extraction utilities to support MCP-specific headers, updating the orchestrator to pass these headers through the JSON-RPC _meta field, and adding support for the FileSearch built-in tool. Feedback is provided regarding a performance optimization in orchestrator.rs to avoid unnecessary string clones by using into_iter() when constructing the metadata map.

Comment thread crates/mcp/src/core/orchestrator.rs
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

1484-1548: ⚠️ Potential issue | 🟠 Major

Request-scoped dynamic tools still bypass _meta.extra_headers.

call_tool_on_peer() fixes the static/pool-backed execution paths, but McpRequestContext::execute_dynamic_tool() in Line 2117 still calls client.call_tool(request) directly. Any tool reached through add_dynamic_server() therefore skips this new header injection entirely, so OCI headers will disappear on that path. Please route that method through call_tool_on_peer() or a shared helper as well.

Supporting change outside this selected range
-        let result = client
-            .call_tool(request)
-            .await
-            .map_err(|e| McpError::ToolExecution(format!("MCP call failed: {e}")))?;
+        let result = self
+            .orchestrator
+            .call_tool_on_peer(
+                client.peer(),
+                entry.server_key(),
+                request,
+                &self.forwarded_headers,
+            )
+            .await?;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/mcp/src/core/orchestrator.rs` around lines 1484 - 1548,
execute_dynamic_tool currently calls client.call_tool(request) directly which
bypasses the new _meta.extra_headers injection; update
McpRequestContext::execute_dynamic_tool to route dynamic tool calls through the
existing call_tool_on_peer (or extract the call logic into a shared helper) so
the forwarded_headers map is passed and injected into Meta like in
call_tool_on_peer; ensure you construct/obtain the same peer
(&rmcp::service::Peer<RoleClient>) and server_key used for error mapping, pass
the original request and forwarded_headers through, and replace direct
client.call_tool(...) calls with call_tool_on_peer(peer, &server_key, request,
&forwarded_headers) (or call the new shared helper) so OCI headers are preserved
for add_dynamic_server paths.
model_gateway/src/routers/common/header_utils.rs (1)

303-330: ⚠️ Potential issue | 🟠 Major

Don't comma-join singleton auth headers.

This helper now applies CSV-style merging to every repeated header. That is fine for tracestate, but it corrupts singleton credentials like authorization, x-smg-oci-delegation-token, and x-smg-oci-compartment-id: two header instances become one synthetic value that downstream auth code cannot interpret reliably. Please keep list semantics only for headers that actually allow it, and otherwise reject or pick a single value.

Possible fix
 fn extract_headers_matching(
     headers: Option<&HeaderMap>,
     predicate: fn(&str) -> bool,
 ) -> HashMap<String, String> {
+    fn supports_csv_join(name: &str) -> bool {
+        name.eq_ignore_ascii_case("tracestate")
+    }
+
     let Some(headers) = headers else {
         return HashMap::new();
     };
@@
-        forwarded
-            .entry(name_str.to_string())
-            .and_modify(|existing: &mut String| {
-                existing.push_str(", ");
-                existing.push_str(value);
-            })
-            .or_insert_with(|| value.to_string());
+        forwarded
+            .entry(name_str.to_string())
+            .and_modify(|existing: &mut String| {
+                if supports_csv_join(name_str) {
+                    existing.push_str(", ");
+                    existing.push_str(value);
+                }
+            })
+            .or_insert_with(|| value.to_string());
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@model_gateway/src/routers/common/header_utils.rs` around lines 303 - 330, The
extract_headers_matching function is currently comma-joining every repeated
header which breaks singleton auth headers; change the merging logic to only
CSV-merge for a allowlist (e.g., "tracestate") and treat other headers
(specifically "authorization", "x-smg-oci-delegation-token",
"x-smg-oci-compartment-id") as singletons: when inserting, if the header is not
in the CSV allowlist then keep the first value and ignore subsequent occurrences
(do not push ", " + value) instead of concatenating; implement this by adding a
small allowlist constant and branching inside the .entry(...).and_modify(...) /
.or_insert_with(...) flow in extract_headers_matching to apply CSV merging only
for allowlisted names and otherwise leave the existing value unchanged.
🤖 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 1459-1462: The doc comment currently references the wrong JSON-RPC
field name; update the comment that mentions `_meta.forwarded_headers` to
instead document `_meta.extra_headers` so it matches the implementation that
serializes forwarded_headers into `_meta.extra_headers`; search for the comment
blocks that describe forwarded_headers (e.g., the comment above the
forwarded_headers handling) and change both occurrences to `_meta.extra_headers`
to keep docs consistent with the code.

---

Outside diff comments:
In `@crates/mcp/src/core/orchestrator.rs`:
- Around line 1484-1548: execute_dynamic_tool currently calls
client.call_tool(request) directly which bypasses the new _meta.extra_headers
injection; update McpRequestContext::execute_dynamic_tool to route dynamic tool
calls through the existing call_tool_on_peer (or extract the call logic into a
shared helper) so the forwarded_headers map is passed and injected into Meta
like in call_tool_on_peer; ensure you construct/obtain the same peer
(&rmcp::service::Peer<RoleClient>) and server_key used for error mapping, pass
the original request and forwarded_headers through, and replace direct
client.call_tool(...) calls with call_tool_on_peer(peer, &server_key, request,
&forwarded_headers) (or call the new shared helper) so OCI headers are preserved
for add_dynamic_server paths.

In `@model_gateway/src/routers/common/header_utils.rs`:
- Around line 303-330: The extract_headers_matching function is currently
comma-joining every repeated header which breaks singleton auth headers; change
the merging logic to only CSV-merge for a allowlist (e.g., "tracestate") and
treat other headers (specifically "authorization", "x-smg-oci-delegation-token",
"x-smg-oci-compartment-id") as singletons: when inserting, if the header is not
in the CSV allowlist then keep the first value and ignore subsequent occurrences
(do not push ", " + value) instead of concatenating; implement this by adding a
small allowlist constant and branching inside the .entry(...).and_modify(...) /
.or_insert_with(...) flow in extract_headers_matching to apply CSV merging only
for allowlisted names and otherwise leave the existing value unchanged.
🪄 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: 5cb57901-f609-40c1-a4a6-bf8fbc6b10fb

📥 Commits

Reviewing files that changed from the base of the PR and between 00ae80b and d9dddc9.

📒 Files selected for processing (5)
  • crates/mcp/src/core/orchestrator.rs
  • model_gateway/src/routers/common/header_utils.rs
  • model_gateway/src/routers/common/mcp_utils.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/src/routers/openai/responses/streaming.rs

Comment thread crates/mcp/src/core/orchestrator.rs
Signed-off-by: aravind <aravindh.raja@oracle.com>
@mergify
Copy link
Copy Markdown
Contributor

mergify Bot commented May 7, 2026

Hi @aravindh-raja, this PR has merge conflicts that must be resolved before it can be merged. Please rebase your branch:

git fetch origin main
git rebase origin/main
# resolve any conflicts, then:
git push --force-with-lease

@mergify mergify Bot added the needs-rebase PR has merge conflicts that need to be resolved label May 7, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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/transform/transformer.rs (1)

243-257: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Parse file-search payloads from MCP text blocks.

call_result_to_json() feeds this transformer the MCP content array. If a file-search server returns a plain JSON object through a text block ([{"type":"text","text":"{\"query\":...,\"results\":[...] }"}]), extract_file_results() at Line 567 only inspects result.as_object(), and the single-query fallback at Line 249 only reads a top-level object. That emits a file_search_call with empty results/queries even though the MCP payload contains hits. Reuse parse_text_block_payload() here the same way the web-search and image-generation paths already do.

Also applies to: 563-599

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/mcp/src/transform/transformer.rs` around lines 243 - 257, The
file-search branch is only inspecting result.as_object() and a single top-level
object for queries, so it misses MCP text-block payloads; update the file-search
path in call_result_to_json (the code building
ResponseOutputItem::FileSearchCall) to reuse parse_text_block_payload() the same
way web-search/image paths do: first call parse_text_block_payload(result) and
if it yields an object/array use that parsed payload for extract_file_results()
and for reading KEY_QUERY (fall back to parsed payload for single-query
extraction), then proceed to build queries and results (using
extract_file_results and the existing queries fallback) so text-block JSON
payloads are handled correctly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@model_gateway/src/routers/common/header_utils.rs`:
- Around line 303-312: Remove "traceparent" from the CSV_MERGE_HEADERS constant
so it is no longer treated as a comma-joinable header; update the
CSV_MERGE_HEADERS array to only include "tracestate" and ensure
is_csv_mergeable(name: &str) continues to use that constant so traceparent is
handled as a singleton (first-value-wins) like other single-value headers.

---

Outside diff comments:
In `@crates/mcp/src/transform/transformer.rs`:
- Around line 243-257: The file-search branch is only inspecting
result.as_object() and a single top-level object for queries, so it misses MCP
text-block payloads; update the file-search path in call_result_to_json (the
code building ResponseOutputItem::FileSearchCall) to reuse
parse_text_block_payload() the same way web-search/image paths do: first call
parse_text_block_payload(result) and if it yields an object/array use that
parsed payload for extract_file_results() and for reading KEY_QUERY (fall back
to parsed payload for single-query extraction), then proceed to build queries
and results (using extract_file_results and the existing queries fallback) so
text-block JSON payloads are handled correctly.
🪄 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: 67a5f803-6a18-4e75-bf9f-01aba006b3a8

📥 Commits

Reviewing files that changed from the base of the PR and between d9dddc9 and bb18999.

📒 Files selected for processing (5)
  • crates/mcp/src/core/orchestrator.rs
  • crates/mcp/src/transform/transformer.rs
  • model_gateway/src/routers/common/header_utils.rs
  • model_gateway/src/routers/openai/responses/non_streaming.rs
  • model_gateway/src/routers/openai/responses/streaming.rs

Comment on lines +303 to +312
/// Headers where multiple values are comma-joined per RFC 9110.
/// All other headers are treated as singletons (first value wins).
const CSV_MERGE_HEADERS: &[&str] = &["tracestate", "traceparent"];

/// Returns `true` if repeated values for `name` should be comma-joined.
fn is_csv_mergeable(name: &str) -> bool {
CSV_MERGE_HEADERS
.iter()
.any(|h| name.eq_ignore_ascii_case(h))
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does the W3C Trace Context specification allow multiple traceparentheaders or comma-joinedtraceparentvalues, or is onlytracestate allowed to be combined from multiple header fields?

💡 Result:

The W3C Trace Context specification does not allow multiple traceparent headers or comma-joined traceparent values. The traceparent header is designed as a single, fixed-format header field representing one incoming request's position in the trace graph [1][2][3]. The specification describes processing a single traceparent header: vendors check for "a traceparent header" and create a new one if absent or invalid [1][2]. It explicitly states the header name is "a single word" without delimiters, and always refers to it in singular form [1][3]. Multiple traceparent headers are treated as invalid by implementations (e.g., Elasticsearch rejects with 400, considering it malformed [4]; Nginx bugs produce comma-joined values incorrectly [5]), aligning with the spec's intent for exactly one valid traceparent per request. In contrast, tracestate is explicitly allowed to be sent or received as multiple header fields, which MUST be combined per RFC 9110 Section 5.3 (or RFC 7230 in older drafts) [1][2][3]. It SHOULD be a single field when possible but MAY be split, enabling combination from multiple sources. No provision exists in the spec (Level 1 Recommendation [2][6] or Level 2 Candidate Recommendation [1]) for multiple or comma-joined traceparent values; such cases lead to ignoring the header and starting a new trace [3].

Citations:


Remove traceparent from the CSV-merge headers list.

The W3C Trace Context specification designates traceparent as a single, fixed-format header field that cannot be comma-joined across multiple header occurrences. Only tracestate is explicitly allowed to be combined from multiple header fields per RFC 9110. Including traceparent in CSV_MERGE_HEADERS (line 305) causes repeated traceparent values to be incorrectly merged into an invalid format like "a, b", which downstream trace processors will reject. Keep traceparent as a singleton (first value wins), treating it like other single-value headers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@model_gateway/src/routers/common/header_utils.rs` around lines 303 - 312,
Remove "traceparent" from the CSV_MERGE_HEADERS constant so it is no longer
treated as a comma-joinable header; update the CSV_MERGE_HEADERS array to only
include "tracestate" and ensure is_csv_mergeable(name: &str) continues to use
that constant so traceparent is handled as a singleton (first-value-wins) like
other single-value headers.

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 needs-rebase PR has merge conflicts that need to be resolved openai OpenAI router changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants