-
Notifications
You must be signed in to change notification settings - Fork 392
propagate headers from the agent to the mcp tools #1252
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Peter Jausovec <[email protected]>
There was a problem hiding this 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
HeaderCaptureMiddlewareto intercept and store HTTP request headers in a context variable - Created
KAgentCallContextBuilderto 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() |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
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.
| headers = get_request_headers() | |
| headers = get_request_headers() or {} |
| 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) |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
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.
| 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 |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
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()| headers = dict(request.headers) | ||
| set_request_headers(headers) |
Copilot
AI
Jan 27, 2026
There was a problem hiding this comment.
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:
- Implementing a whitelist of safe headers to propagate (e.g., only
x-user-id,authorization, and other explicitly needed headers) - Sanitizing or excluding sensitive headers like
cookie,x-api-key, etc. - Documenting which headers are intended to be propagated and why
This reduces the risk of inadvertently leaking sensitive information to downstream components or logs.
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.