@@ -11,6 +11,7 @@ import (
1111
1212 "github.com/a2aproject/a2a-go/a2asrv"
1313 "github.com/go-logr/logr"
14+ "github.com/kagent-dev/kagent/go/adk/pkg/constants"
1415 "github.com/kagent-dev/kagent/go/api/adk"
1516 mcpsdk "github.com/modelcontextprotocol/go-sdk/mcp"
1617 "google.golang.org/adk/tool"
@@ -62,6 +63,7 @@ type mcpServerParams struct {
6263 URL string
6364 Headers map [string ]string
6465 AllowedHeaders []string // header names to forward from incoming request
66+ PropagateToken bool // when true, Authorization is forwarded independently of AllowedHeaders
6567 ServerType string // "http" or "sse"
6668 Timeout * float64
6769 SseReadTimeout * float64
@@ -73,7 +75,11 @@ type mcpServerParams struct {
7375// CreateToolsets creates toolsets from all configured HTTP and SSE MCP servers,
7476// returning the accumulated toolsets. Errors on individual servers are logged
7577// and skipped.
76- func CreateToolsets (ctx context.Context , httpTools []adk.HttpMcpServerConfig , sseTools []adk.SseMcpServerConfig ) []tool.Toolset {
78+ //
79+ // When propagateToken is true, Authorization is forwarded to every MCP server
80+ // independently of AllowedHeaders, mirroring the Python ADKTokenPropagationPlugin
81+ // behaviour triggered by KAGENT_PROPAGATE_TOKEN.
82+ func CreateToolsets (ctx context.Context , httpTools []adk.HttpMcpServerConfig , sseTools []adk.SseMcpServerConfig , propagateToken bool ) []tool.Toolset {
7783 log := logr .FromContextOrDiscard (ctx )
7884 var toolsets []tool.Toolset
7985
@@ -83,6 +89,7 @@ func CreateToolsets(ctx context.Context, httpTools []adk.HttpMcpServerConfig, ss
8389 URL : httpTool .Params .Url ,
8490 Headers : httpTool .Params .Headers ,
8591 AllowedHeaders : httpTool .AllowedHeaders ,
92+ PropagateToken : propagateToken ,
8693 ServerType : "http" ,
8794 Timeout : httpTool .Params .Timeout ,
8895 SseReadTimeout : httpTool .Params .SseReadTimeout ,
@@ -103,6 +110,7 @@ func CreateToolsets(ctx context.Context, httpTools []adk.HttpMcpServerConfig, ss
103110 URL : sseTool .Params .Url ,
104111 Headers : sseTool .Params .Headers ,
105112 AllowedHeaders : sseTool .AllowedHeaders ,
113+ PropagateToken : propagateToken ,
106114 ServerType : "sse" ,
107115 Timeout : sseTool .Params .Timeout ,
108116 SseReadTimeout : sseTool .Params .SseReadTimeout ,
@@ -200,11 +208,12 @@ func createTransport(ctx context.Context, params mcpServerParams) (mcpsdk.Transp
200208 }
201209
202210 var httpTransport http.RoundTripper = baseTransport
203- if len (params .Headers ) > 0 || len (params .AllowedHeaders ) > 0 {
211+ if len (params .Headers ) > 0 || len (params .AllowedHeaders ) > 0 || params . PropagateToken {
204212 httpTransport = & headerRoundTripper {
205213 base : baseTransport ,
206214 headers : params .Headers ,
207215 allowedHeaders : params .AllowedHeaders ,
216+ propagateToken : params .PropagateToken ,
208217 }
209218 }
210219
@@ -230,30 +239,41 @@ func createTransport(ctx context.Context, params mcpServerParams) (mcpsdk.Transp
230239}
231240
232241// headerRoundTripper wraps an http.RoundTripper to add custom headers to all
233- // requests. It supports two sources of headers:
234- // - headers: static key/value pairs configured on the MCP server spec
235- // - allowedHeaders: header names to forward from the incoming A2A request;
236- // values are read on each call via allowedRequestHeaders directly from the
237- // A2A CallContext that is already present in the Go context.
238- //
239- // Static headers take precedence: if an allowed header has the same name as a
240- // static header, the static value wins.
242+ // requests. It supports three sources of headers, applied in this order so that
243+ // higher-priority sources win on collision:
244+ // 1. propagateToken: when true, Authorization is read from the incoming A2A
245+ // CallContext and forwarded unconditionally (independent of allowedHeaders).
246+ // 2. allowedHeaders: explicit per-header forwarding from the A2A CallContext.
247+ // 3. headers: static key/value pairs configured on the MCP server spec (highest
248+ // priority — always wins).
241249type headerRoundTripper struct {
242250 base http.RoundTripper
243251 headers map [string ]string
244252 allowedHeaders []string // header names (case-insensitive) to forward from A2A context
253+ propagateToken bool // when true, Authorization is forwarded independently
245254}
246255
247256func (rt * headerRoundTripper ) RoundTrip (req * http.Request ) (* http.Response , error ) {
248257 req = req .Clone (req .Context ())
249258
250- // Forward allowed headers from the incoming A2A request first so that
251- // static headers can override them if there is a name collision.
259+ // When KAGENT_PROPAGATE_TOKEN is set, forward Authorization from the incoming
260+ // A2A request independently of allowedHeaders.
261+ if rt .propagateToken {
262+ if callCtx , ok := a2asrv .CallContextFrom (req .Context ()); ok {
263+ if meta := callCtx .RequestMeta (); meta != nil {
264+ if vals , ok := meta .Get (constants .AuthorizationHeader ); ok && len (vals ) > 0 && vals [0 ] != "" {
265+ req .Header .Set (constants .AuthorizationHeader , vals [0 ])
266+ }
267+ }
268+ }
269+ }
270+
271+ // Forward explicitly allowed headers from the incoming A2A request.
252272 for k , v := range allowedRequestHeaders (req .Context (), rt .allowedHeaders ) {
253273 req .Header .Set (k , v )
254274 }
255275
256- // Apply static headers (override any dynamic ones with the same name) .
276+ // Apply static headers last — they take precedence over all dynamic sources .
257277 for key , value := range rt .headers {
258278 req .Header .Set (key , value )
259279 }
0 commit comments