Skip to content

Commit c9aee39

Browse files
Copilotg3force
andcommitted
Remove backward compatibility - always use propagate_headers configuration
Co-authored-by: g3force <779094+g3force@users.noreply.github.com>
1 parent d85e433 commit c9aee39

File tree

5 files changed

+19
-205
lines changed

5 files changed

+19
-205
lines changed

adk/README.md

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ The JSON configuration for `AGENT_TOOLS` should follow this structure:
7373
"tool_name": {
7474
"url": "https://mcp-tool-endpoint:8000/mcp",
7575
"timeout": 30, // Optional: connect timeout in seconds (default: 30)
76-
"propagate_headers": ["X-API-Key", "Authorization"] // Optional: list of headers to propagate
76+
"propagate_headers": ["X-API-Key", "Authorization"] // Optional: list of headers to propagate (default: [])
7777
}
7878
}
7979
```
@@ -86,7 +86,7 @@ You can configure which HTTP headers are passed from the incoming A2A request to
8686
- **Per-server configuration**: Each MCP server can receive different headers
8787
- **Security**: Headers are only sent to servers explicitly configured to receive them
8888
- **Case-insensitive matching**: Header names are matched case-insensitively
89-
- **Backward compatibility**: When `propagate_headers` is not specified, the legacy behavior is used (only `X-External-Token` is passed)
89+
- **Default behavior**: When `propagate_headers` is not specified or is empty, no headers are passed
9090

9191
**Example configuration:**
9292
```json5
@@ -101,7 +101,7 @@ You can configure which HTTP headers are passed from the incoming A2A request to
101101
},
102102
"public_tool": {
103103
"url": "https://public-mcp.example.com/mcp"
104-
// No propagate_headers - only X-External-Token will be passed (legacy behavior)
104+
// No propagate_headers - no headers will be passed
105105
}
106106
}
107107
```
@@ -188,24 +188,11 @@ Based on the configuration above:
188188
- `weather_api` MCP server will receive `X-API-Key` and `X-User-Location` headers
189189
- `database_tool` MCP server will receive only the `Authorization` header
190190

191-
### Backward Compatibility
192-
193-
For backward compatibility, if `propagate_headers` is not specified in the configuration, the SDK will use legacy behavior: only the `X-External-Token` header is passed to the MCP server.
194-
195-
```json5
196-
{
197-
"legacy_tool": {
198-
"url": "https://legacy-mcp.example.com/mcp"
199-
// No propagate_headers - only X-External-Token will be passed
200-
}
201-
}
202-
```
203-
204191
**Limitations**: Header propagation is only supported for MCP servers. Propagation to sub-agents is not currently supported due to ADK limitations in passing custom HTTP headers in A2A requests.
205192

206193
### Security Considerations
207194

208-
- Tokens are stored in ADK session state (separate from memory state that the LLM can access)
209-
- Tokens are not directly accessible to agent code through normal session state queries
210-
- Tokens persist for the session duration and are managed by ADK's session lifecycle
195+
- Headers are stored in ADK session state (separate from memory state that the LLM can access)
196+
- Headers are not directly accessible to agent code through normal session state queries
197+
- Headers persist for the session duration and are managed by ADK's session lifecycle
211198
- This is a simple authentication mechanism; for production use, consider implementing more sophisticated authentication and authorization schemes

adk/agenticlayer/agent.py

Lines changed: 3 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,33 +18,11 @@
1818
from httpx_retries import Retry, RetryTransport
1919

2020
from agenticlayer.config import InteractionType, McpTool, SubAgent
21-
from agenticlayer.constants import EXTERNAL_TOKEN_SESSION_KEY, HTTP_HEADERS_SESSION_KEY
21+
from agenticlayer.constants import HTTP_HEADERS_SESSION_KEY
2222

2323
logger = logging.getLogger(__name__)
2424

2525

26-
def _get_mcp_headers_from_session(readonly_context: ReadonlyContext) -> dict[str, str]:
27-
"""Header provider function for MCP tools that retrieves token from ADK session.
28-
29-
This function is called by the ADK when MCP tools are invoked. It reads the
30-
external token from the session state where it was stored during request
31-
processing by TokenCapturingA2aAgentExecutor.
32-
33-
Args:
34-
readonly_context: The ADK ReadonlyContext providing access to the session
35-
36-
Returns:
37-
A dictionary of headers to include in MCP tool requests.
38-
If a token is stored in the session, includes it in the headers.
39-
"""
40-
# Access the session state directly from the readonly context
41-
if readonly_context and readonly_context.state:
42-
external_token = readonly_context.state.get(EXTERNAL_TOKEN_SESSION_KEY)
43-
if external_token:
44-
return {"X-External-Token": external_token}
45-
return {}
46-
47-
4826
def _create_header_provider(propagate_headers: list[str]) -> Callable[[ReadonlyContext], dict[str, str]]:
4927
"""Create a header provider function for a specific MCP server.
5028
@@ -185,13 +163,8 @@ def load_tools(self, mcp_tools: list[McpTool]) -> list[ToolUnion]:
185163
for tool in mcp_tools:
186164
logger.info(f"Loading tool {tool.model_dump_json()}")
187165

188-
# Use configured header provider if propagate_headers is specified,
189-
# otherwise fall back to legacy behavior (x-external-token only)
190-
if tool.propagate_headers is not None:
191-
header_provider = _create_header_provider(tool.propagate_headers)
192-
else:
193-
# Backward compatibility: use legacy provider that only sends x-external-token
194-
header_provider = _get_mcp_headers_from_session
166+
# Create header provider with configured headers
167+
header_provider = _create_header_provider(tool.propagate_headers)
195168

196169
tools.append(
197170
McpToolset(

adk/agenticlayer/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class McpTool(BaseModel):
2424
name: str
2525
url: AnyHttpUrl
2626
timeout: int = 30
27-
propagate_headers: list[str] | None = None
27+
propagate_headers: list[str] = []
2828

2929

3030
def parse_sub_agents(sub_agents_config: str) -> list[SubAgent]:

adk/tests/test_agent_integration.py

Lines changed: 9 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -407,9 +407,16 @@ async def handler_with_header_capture(request: httpx.Request) -> httpx.Response:
407407
# Route MCP requests through our custom handler
408408
respx_mock.route(host="test-mcp-token.local").mock(side_effect=handler_with_header_capture)
409409

410-
# When: Create agent with MCP tool and send request with X-External-Token header
410+
# When: Create agent with MCP tool configured to propagate X-External-Token and send request with header
411411
test_agent = agent_factory("test_agent")
412-
tools = [McpTool(name="verifier", url=AnyHttpUrl(f"{mcp_server_url}/mcp"), timeout=30)]
412+
tools = [
413+
McpTool(
414+
name="verifier",
415+
url=AnyHttpUrl(f"{mcp_server_url}/mcp"),
416+
timeout=30,
417+
propagate_headers=["X-External-Token"],
418+
)
419+
]
413420
external_token = "secret-api-token-12345" # nosec B105
414421

415422
async with app_factory(test_agent, tools=tools) as app:
@@ -673,90 +680,6 @@ async def handler2(request: httpx.Request) -> httpx.Response:
673680
assert "x-server1-token" not in headers_lower
674681
assert "x-common-header" not in headers_lower
675682

676-
@pytest.mark.asyncio
677-
async def test_backward_compatibility_no_propagate_headers(
678-
self,
679-
app_factory: Any,
680-
agent_factory: Any,
681-
llm_controller: LLMMockController,
682-
respx_mock: respx.MockRouter,
683-
) -> None:
684-
"""Test backward compatibility: when propagate_headers is not set, X-External-Token is still passed."""
685-
686-
# Given: Mock LLM to call 'echo' tool
687-
llm_controller.respond_with_tool_call(
688-
pattern="",
689-
tool_name="echo",
690-
tool_args={"message": "test"},
691-
final_message="Echo completed!",
692-
)
693-
694-
# Given: MCP server
695-
mcp = FastMCP("BackwardCompatVerifier")
696-
received_headers: list[dict[str, str]] = []
697-
698-
@mcp.tool()
699-
def echo(message: str) -> str:
700-
"""Echo a message."""
701-
return f"Echoed: {message}"
702-
703-
mcp_server_url = "http://test-backward-compat.local"
704-
mcp_app = mcp.http_app(path="/mcp")
705-
706-
async with LifespanManager(mcp_app) as mcp_manager:
707-
# Create a custom handler that captures headers
708-
async def handler_with_header_capture(request: httpx.Request) -> httpx.Response:
709-
received_headers.append(dict(request.headers))
710-
transport = httpx.ASGITransport(app=mcp_manager.app)
711-
async with httpx.AsyncClient(transport=transport, base_url=mcp_server_url) as client:
712-
return await client.request(
713-
method=request.method,
714-
url=str(request.url),
715-
headers=request.headers,
716-
content=request.content,
717-
)
718-
719-
respx_mock.route(host="test-backward-compat.local").mock(side_effect=handler_with_header_capture)
720-
721-
# When: Create agent with MCP tool WITHOUT propagate_headers config
722-
test_agent = agent_factory("test_agent")
723-
tools = [
724-
McpTool(
725-
name="verifier",
726-
url=AnyHttpUrl(f"{mcp_server_url}/mcp"),
727-
timeout=30,
728-
# No propagate_headers specified - should use legacy behavior
729-
)
730-
]
731-
external_token = "legacy-token-12345" # nosec B105
732-
733-
async with app_factory(test_agent, tools=tools) as app:
734-
client = TestClient(app)
735-
response = client.post(
736-
"",
737-
json=create_send_message_request("Test message"),
738-
headers={
739-
"X-External-Token": external_token,
740-
"X-Other-Header": "should-not-be-sent",
741-
},
742-
)
743-
744-
# Then: Verify response is successful
745-
assert response.status_code == 200
746-
result = verify_jsonrpc_response(response.json())
747-
assert result["status"]["state"] == "completed", "Task should complete successfully"
748-
749-
# Then: Verify X-External-Token was passed (legacy behavior)
750-
assert len(received_headers) > 0, "MCP server should have received requests"
751-
tool_call_headers = [h for h in received_headers if "x-external-token" in h or "X-External-Token" in h]
752-
assert len(tool_call_headers) > 0, "At least one request should have X-External-Token header"
753-
754-
for headers in tool_call_headers:
755-
headers_lower = {k.lower(): v for k, v in headers.items()}
756-
assert headers_lower.get("x-external-token") == external_token
757-
# Other headers should NOT be passed
758-
assert "x-other-header" not in headers_lower
759-
760683
@pytest.mark.asyncio
761684
async def test_explicitly_empty_propagate_headers_sends_no_headers(
762685
self,

adk/tests/test_external_token.py

Lines changed: 0 additions & 69 deletions
This file was deleted.

0 commit comments

Comments
 (0)