Problem
Named provider profiles ([providers.X]) can customize the request body via extra_body (#341) but have no way to attach custom HTTP headers. Some OpenAI-compatible gateways make per-request behavior decisions based on headers, and clients that can't send them are stuck with gateway-global config.
Concrete case: Bifrost's MCP auto tool injection. When a Bifrost gateway has MCP clients connected, it appends those tools to every inference request's tools array (gateway-side names like exa_mcp-web_search_exa). Bifrost's default execution mode is stateless, so calls to those tools come back to the caller unexecuted. jcode — which registers its own MCP tools (e.g. bridged to the same gateway's /mcp endpoint via stdio) — then receives calls to tool names it doesn't own and refuses them. The model sees two names for the same tool and picks the unexecutable one.
Bifrost's documented per-request opt-out is an empty x-bf-mcp-include-tools header (deny-all). There is currently no way to make jcode send it, so jcode's correctness depends on a gateway-wide setting (mcp_disable_auto_tool_inject), which the client should not have to rely on.
Proposal
Add extra_headers to named provider profiles, mirroring extra_body:
[providers.bifrost]
type = openai-compatible
base_url = http://bifrost:4000/v1
extra_headers = { x-bf-mcp-include-tools = } # empty = deny-all injection opt-out
- Attached to every chat/completions request, after jcode's own headers.
- Empty values are preserved (present-but-empty is the opt-out signal).
- Invalid header names are logged and skipped at construction rather than failing provider init.
Related: #761 (native Streamable HTTP MCP would make the bridge unnecessary, but the header need is orthogonal).
Implementation ready: branch on my fork (marko-k0/jcode, feat/provider-extra-headers), with unit + wire-level tests.
Problem
Named provider profiles (
[providers.X]) can customize the request body viaextra_body(#341) but have no way to attach custom HTTP headers. Some OpenAI-compatible gateways make per-request behavior decisions based on headers, and clients that can't send them are stuck with gateway-global config.Concrete case: Bifrost's MCP auto tool injection. When a Bifrost gateway has MCP clients connected, it appends those tools to every inference request's tools array (gateway-side names like
exa_mcp-web_search_exa). Bifrost's default execution mode is stateless, so calls to those tools come back to the caller unexecuted. jcode — which registers its own MCP tools (e.g. bridged to the same gateway's/mcpendpoint via stdio) — then receives calls to tool names it doesn't own and refuses them. The model sees two names for the same tool and picks the unexecutable one.Bifrost's documented per-request opt-out is an empty
x-bf-mcp-include-toolsheader (deny-all). There is currently no way to make jcode send it, so jcode's correctness depends on a gateway-wide setting (mcp_disable_auto_tool_inject), which the client should not have to rely on.Proposal
Add
extra_headersto named provider profiles, mirroringextra_body:Related: #761 (native Streamable HTTP MCP would make the bridge unnecessary, but the header need is orthogonal).
Implementation ready: branch on my fork (marko-k0/jcode,
feat/provider-extra-headers), with unit + wire-level tests.