Skip to content

Conversation

@peterj
Copy link
Collaborator

@peterj peterj commented Jan 27, 2026

HeaderCaptureMiddleware intercepts the incoming HTTP requests and copies headers into a context variable. The KAgentCallContextBuilder reads that and stores the headers in ServerCallContext.state["headers"].

Once the headers are in the context, the agent executor can read them and store them in the sessions state for any plugins to extract them/use them.

Copilot AI review requested due to automatic review settings January 27, 2026 23:02
@peterj peterj closed this Jan 27, 2026
@peterj peterj deleted the peterj/headerprop branch January 27, 2026 23:04
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements HTTP header propagation from incoming requests through the agent execution pipeline to MCP tools. The implementation uses middleware to capture headers, stores them in a ContextVar, and makes them available in the ServerCallContext and session state.

Changes:

  • Introduced HeaderCaptureMiddleware to intercept and store HTTP request headers in a context variable
  • Created KAgentCallContextBuilder to bridge the middleware with the A2A framework's ServerCallContext
  • Added context management utilities (set_request_headers, get_request_headers, clear_request_headers) for request-scoped header storage

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
python/packages/kagent-core/src/kagent/core/a2a/_context.py New file introducing context variables for storing HTTP headers with getter/setter/clear functions
python/packages/kagent-core/src/kagent/core/a2a/_requests.py Adds KAgentCallContextBuilder that reads headers from ContextVar and stores them in ServerCallContext state
python/packages/kagent-core/src/kagent/core/a2a/init.py Exports new KAgentCallContextBuilder and context management functions
python/packages/kagent-adk/src/kagent/adk/_a2a.py Implements HeaderCaptureMiddleware and integrates KAgentCallContextBuilder into the A2A application
python/packages/kagent-adk/src/kagent/adk/_agent_executor.py Minor whitespace fix in comment (trailing space removal)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

"""

def build(self, request: Request) -> ServerCallContext:
headers = get_request_headers()
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

If get_request_headers() returns None (which it will when the ContextVar isn't set), this will pass None into the ServerCallContext state. Downstream code expects headers to be a dictionary (e.g., in _agent_executor.py line 205 and _requests.py line 63, it's used with .get()). This will work with the fallback {} in those locations, but it's inconsistent and could cause issues if other code accesses state["headers"] directly without a default.

This issue is related to the bug in _context.py where the default should be {} instead of None.

Suggested change
headers = get_request_headers()
headers = get_request_headers() or {}

Copilot uses AI. Check for mistakes.
from typing import Dict

# Context variable to store HTTP request headers for the current request
_request_headers_var: ContextVar[Dict[str, str]] = ContextVar("request_headers", default=None)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The ContextVar has default=None but get_request_headers() is documented to return Dict[str, str]. When the context variable is not set, get() will return None, which contradicts the return type annotation and the docstring that says it returns an empty dict. This will cause a TypeError when the code tries to use the returned value as a dict (e.g., in line 42 of _requests.py where it's passed to ServerCallContext state, or in line 205 of _agent_executor.py where .get() is called on it).

The default should be default={} to match the documented behavior, or the function should explicitly handle the None case and return an empty dict.

Copilot uses AI. Check for mistakes.
Comment on lines +61 to +67
async def dispatch(self, request: Request, call_next):
headers = dict(request.headers)
set_request_headers(headers)
logger.debug(f"Captured {len(headers)} HTTP headers for request")

response = await call_next(request)
return response
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The middleware captures headers but doesn't clear them after the request is processed. While ContextVars are request-scoped in async contexts, explicitly clearing headers after request completion would prevent potential issues with context leakage or stale data, especially in error scenarios. Consider adding a try-finally block to ensure headers are cleared even if the request fails.

For example:

async def dispatch(self, request: Request, call_next):
    headers = dict(request.headers)
    set_request_headers(headers)
    logger.debug(f"Captured {len(headers)} HTTP headers for request")
    
    try:
        response = await call_next(request)
        return response
    finally:
        clear_request_headers()

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +63
headers = dict(request.headers)
set_request_headers(headers)
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

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

The middleware captures ALL request headers without filtering, including potentially sensitive ones like cookies, authorization tokens, and other security-related headers. These are then stored in the session state and potentially passed to MCP tools. Consider:

  1. Implementing a whitelist of safe headers to propagate (e.g., only x-user-id, authorization, and other explicitly needed headers)
  2. Sanitizing or excluding sensitive headers like cookie, x-api-key, etc.
  3. Documenting which headers are intended to be propagated and why

This reduces the risk of inadvertently leaking sensitive information to downstream components or logs.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants