From 5342658b17fa651ce743b0dc0d1fc665d5cc9c37 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 16:12:49 -0500 Subject: [PATCH 1/6] feat: add gateway auth support to agent templates Add SigV4 authentication to MCP client templates so agents can authenticate with AWS_IAM gateways. Each framework's client.py uses Handlebars conditionals to include auth when gateways exist. SigV4HTTPXAuth class signs HTTP requests using botocore SigV4Auth, passed to the MCP client via httpx.AsyncClient. Templates read gateway URLs from AGENTCORE_GATEWAY_{NAME}_URL env vars and handle missing vars gracefully (warn, don't crash). Updated all 5 frameworks: Strands, LangChain, OpenAI Agents, Google ADK, AutoGen. Schema mapper reads mcp.json to populate gateway config for template rendering. All gateways are auto- included when creating an agent. --- .../assets.snapshot.test.ts.snap | 333 ++++++++++++++++-- src/assets/python/autogen/base/main.py | 2 + .../python/autogen/base/mcp_client/client.py | 51 ++- src/assets/python/googleadk/base/main.py | 3 +- .../googleadk/base/mcp_client/client.py | 52 ++- .../python/langchain_langgraph/base/main.py | 4 +- .../base/mcp_client/client.py | 70 +++- src/assets/python/openaiagents/base/main.py | 16 +- .../openaiagents/base/mcp_client/client.py | 59 +++- src/assets/python/strands/base/main.py | 8 +- .../python/strands/base/mcp_client/client.py | 57 ++- src/cli/commands/add/actions.ts | 2 +- src/cli/commands/create/action.ts | 2 +- .../generate/__tests__/schema-mapper.test.ts | 22 +- .../agent/generate/schema-mapper.ts | 32 +- src/cli/templates/types.ts | 12 + src/cli/tui/screens/agent/useAddAgent.ts | 2 +- src/cli/tui/screens/create/useCreateFlow.ts | 2 +- .../tui/screens/generate/useGenerateFlow.ts | 2 +- 19 files changed, 662 insertions(+), 69 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 89ef1df5..da161d8d 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -944,6 +944,8 @@ async def invoke(payload, context): # Get MCP Tools mcp_tools = await get_streamable_http_mcp_tools() + if mcp_tools is None: + mcp_tools = [] # Define an AssistantAgent with the model and tools agent = AssistantAgent( @@ -974,24 +976,69 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/au `; exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/mcp_client/client.py should match snapshot 1`] = ` -"from typing import List +"import os +import logging +from typing import List from autogen_ext.tools.mcp import ( StreamableHttpMcpToolAdapter, StreamableHttpServerParams, mcp_server_tools, ) +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: + """Returns MCP Tools from the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return [] + + server_params = StreamableHttpServerParams(url=url) + return await mcp_server_tools(server_params) +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """ - Returns MCP Tools compatible with AutoGen. - """ + """Returns MCP Tools compatible with AutoGen.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} server_params = StreamableHttpServerParams(url=EXAMPLE_MCP_ENDPOINT) return await mcp_server_tools(server_params) +{{/if}} " `; @@ -1616,7 +1663,8 @@ def add_numbers(a: int, b: int) -> int: # Get MCP Toolset -mcp_toolset = [get_streamable_http_mcp_client()] +mcp_client = get_streamable_http_mcp_client() +mcp_toolset = [mcp_client] if mcp_client else [] _credentials_loaded = False @@ -1691,21 +1739,67 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/go `; exports[`Assets Directory Snapshots > Python framework assets > python/python/googleadk/base/mcp_client/client.py should match snapshot 1`] = ` -"from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset +"import os +import logging +from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +def get_streamable_http_mcp_client() -> MCPToolset | None: + """Returns an MCP Toolset connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + return MCPToolset( + connection_params=StreamableHTTPConnectionParams(url=url) + ) +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MCPToolset: - """ - Returns an MCP Toolset compatible with Google ADK. - """ + """Returns an MCP Toolset compatible with Google ADK.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} return MCPToolset( connection_params=StreamableHTTPConnectionParams(url=EXAMPLE_MCP_ENDPOINT) ) +{{/if}} " `; @@ -1911,7 +2005,9 @@ async def invoke(payload, context): mcp_client = get_streamable_http_mcp_client() # Load MCP Tools - mcp_tools = await mcp_client.get_tools() + mcp_tools = [] + if mcp_client: + mcp_tools = await mcp_client.get_tools() # Define the agent using create_react_agent graph = create_react_agent(get_or_create_model(), tools=mcp_tools + tools) @@ -1937,16 +2033,79 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/la `; exports[`Assets Directory Snapshots > Python framework assets > python/python/langchain_langgraph/base/mcp_client/client.py should match snapshot 1`] = ` -"from langchain_mcp_adapters.client import MultiServerMCPClient +"import os +import logging +from langchain_mcp_adapters.client import MultiServerMCPClient + +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + +def get_streamable_http_mcp_client() -> MultiServerMCPClient | None: + """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) + return MultiServerMCPClient( + { + "{{gatewayProviders.[0].name}}": { + "transport": "streamable_http", + "url": url, + "http_client": http_client, + } + } + ) + {{else}} + return MultiServerMCPClient( + { + "{{gatewayProviders.[0].name}}": { + "transport": "streamable_http", + "url": url, + } + } + ) + {{/if}} +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MultiServerMCPClient: - """ - Returns an MCP Client compatible with LangChain/LangGraph. - """ + """Returns an MCP Client compatible with LangChain/LangGraph.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} return MultiServerMCPClient( { @@ -1956,6 +2115,7 @@ def get_streamable_http_mcp_client() -> MultiServerMCPClient: } } ) +{{/if}} " `; @@ -2252,12 +2412,22 @@ def add_numbers(a: int, b: int) -> int: async def main(query): ensure_credentials_loaded() try: - async with mcp_server as server: - active_servers = [server] if server else [] + if mcp_server: + async with mcp_server as server: + active_servers = [server] + agent = Agent( + name="{{ name }}", + model="gpt-4.1", + mcp_servers=active_servers, + tools=[add_numbers] + ) + result = await Runner.run(agent, query) + return result + else: agent = Agent( name="{{ name }}", model="gpt-4.1", - mcp_servers=active_servers, + mcp_servers=[], tools=[add_numbers] ) result = await Runner.run(agent, query) @@ -2292,20 +2462,73 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/op `; exports[`Assets Directory Snapshots > Python framework assets > python/python/openaiagents/base/mcp_client/client.py should match snapshot 1`] = ` -"from agents.mcp import MCPServerStreamableHttp +"import os +import logging +from agents.mcp import MCPServerStreamableHttp + +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + +def get_streamable_http_mcp_client() -> MCPServerStreamableHttp | None: + """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) + return MCPServerStreamableHttp( + name="{{gatewayProviders.[0].name}}", params={"url": url, "http_client": http_client} + ) + {{else}} + return MCPServerStreamableHttp( + name="{{gatewayProviders.[0].name}}", params={"url": url} + ) + {{/if}} +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MCPServerStreamableHttp: - """ - Returns an MCP Client compatible with OpenAI Agents SDK. - """ + """Returns an MCP Client compatible with OpenAI Agents SDK.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} return MCPServerStreamableHttp( name="AgentCore Gateway MCP", params={"url": EXAMPLE_MCP_ENDPOINT} ) +{{/if}} " `; @@ -2490,6 +2713,10 @@ def add_numbers(a: int, b: int) -> int: return a+b tools.append(add_numbers) +# Add MCP client to tools if available +if mcp_client: + tools.append(mcp_client) + {{#if hasMemory}} def agent_factory(): @@ -2504,7 +2731,7 @@ def agent_factory(): system_prompt=""" You are a helpful assistant. Use tools when appropriate. """, - tools=tools+[mcp_client] + tools=tools ) return cache[key] return get_or_create_agent @@ -2520,7 +2747,7 @@ def get_or_create_agent(): system_prompt=""" You are a helpful assistant. Use tools when appropriate. """, - tools=tools+[mcp_client] + tools=tools ) return _agent {{/if}} @@ -2558,18 +2785,68 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/st `; exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/mcp_client/client.py should match snapshot 1`] = ` -"from mcp.client.streamable_http import streamablehttp_client +"import os +import logging +from mcp.client.streamable_http import streamablehttp_client from strands.tools.mcp.mcp_client import MCPClient +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +def get_streamable_http_mcp_client() -> MCPClient | None: + """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) + return MCPClient(lambda: streamablehttp_client(url, http_client=http_client)) + {{else}} + return MCPClient(lambda: streamablehttp_client(url)) + {{/if}} +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MCPClient: - """ - Returns an MCP Client compatible with Strands - """ + """Returns an MCP Client compatible with Strands""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} - return MCPClient(lambda: streamablehttp_client(EXAMPLE_MCP_ENDPOINT))" + return MCPClient(lambda: streamablehttp_client(EXAMPLE_MCP_ENDPOINT)) +{{/if}} +" `; exports[`Assets Directory Snapshots > Python framework assets > python/python/strands/base/model/__init__.py should match snapshot 1`] = ` diff --git a/src/assets/python/autogen/base/main.py b/src/assets/python/autogen/base/main.py index 43789e63..2eb4dbba 100644 --- a/src/assets/python/autogen/base/main.py +++ b/src/assets/python/autogen/base/main.py @@ -29,6 +29,8 @@ async def invoke(payload, context): # Get MCP Tools mcp_tools = await get_streamable_http_mcp_tools() + if mcp_tools is None: + mcp_tools = [] # Define an AssistantAgent with the model and tools agent = AssistantAgent( diff --git a/src/assets/python/autogen/base/mcp_client/client.py b/src/assets/python/autogen/base/mcp_client/client.py index 7f4c5c3b..94d184cb 100644 --- a/src/assets/python/autogen/base/mcp_client/client.py +++ b/src/assets/python/autogen/base/mcp_client/client.py @@ -1,3 +1,5 @@ +import os +import logging from typing import List from autogen_ext.tools.mcp import ( StreamableHttpMcpToolAdapter, @@ -5,14 +7,57 @@ mcp_server_tools, ) +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: + """Returns MCP Tools from the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return [] + + server_params = StreamableHttpServerParams(url=url) + return await mcp_server_tools(server_params) +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """ - Returns MCP Tools compatible with AutoGen. - """ + """Returns MCP Tools compatible with AutoGen.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} server_params = StreamableHttpServerParams(url=EXAMPLE_MCP_ENDPOINT) return await mcp_server_tools(server_params) +{{/if}} diff --git a/src/assets/python/googleadk/base/main.py b/src/assets/python/googleadk/base/main.py index 2e89f01a..f830b873 100644 --- a/src/assets/python/googleadk/base/main.py +++ b/src/assets/python/googleadk/base/main.py @@ -23,7 +23,8 @@ def add_numbers(a: int, b: int) -> int: # Get MCP Toolset -mcp_toolset = [get_streamable_http_mcp_client()] +mcp_client = get_streamable_http_mcp_client() +mcp_toolset = [mcp_client] if mcp_client else [] _credentials_loaded = False diff --git a/src/assets/python/googleadk/base/mcp_client/client.py b/src/assets/python/googleadk/base/mcp_client/client.py index 777e2836..9a344b08 100644 --- a/src/assets/python/googleadk/base/mcp_client/client.py +++ b/src/assets/python/googleadk/base/mcp_client/client.py @@ -1,15 +1,61 @@ +import os +import logging from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +def get_streamable_http_mcp_client() -> MCPToolset | None: + """Returns an MCP Toolset connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + return MCPToolset( + connection_params=StreamableHTTPConnectionParams(url=url) + ) +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MCPToolset: - """ - Returns an MCP Toolset compatible with Google ADK. - """ + """Returns an MCP Toolset compatible with Google ADK.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} return MCPToolset( connection_params=StreamableHTTPConnectionParams(url=EXAMPLE_MCP_ENDPOINT) ) +{{/if}} diff --git a/src/assets/python/langchain_langgraph/base/main.py b/src/assets/python/langchain_langgraph/base/main.py index 88bfe2d8..1447a3e7 100644 --- a/src/assets/python/langchain_langgraph/base/main.py +++ b/src/assets/python/langchain_langgraph/base/main.py @@ -37,7 +37,9 @@ async def invoke(payload, context): mcp_client = get_streamable_http_mcp_client() # Load MCP Tools - mcp_tools = await mcp_client.get_tools() + mcp_tools = [] + if mcp_client: + mcp_tools = await mcp_client.get_tools() # Define the agent using create_react_agent graph = create_react_agent(get_or_create_model(), tools=mcp_tools + tools) diff --git a/src/assets/python/langchain_langgraph/base/mcp_client/client.py b/src/assets/python/langchain_langgraph/base/mcp_client/client.py index 1e870204..3d2a3260 100644 --- a/src/assets/python/langchain_langgraph/base/mcp_client/client.py +++ b/src/assets/python/langchain_langgraph/base/mcp_client/client.py @@ -1,13 +1,76 @@ +import os +import logging from langchain_mcp_adapters.client import MultiServerMCPClient +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +def get_streamable_http_mcp_client() -> MultiServerMCPClient | None: + """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) + return MultiServerMCPClient( + { + "{{gatewayProviders.[0].name}}": { + "transport": "streamable_http", + "url": url, + "http_client": http_client, + } + } + ) + {{else}} + return MultiServerMCPClient( + { + "{{gatewayProviders.[0].name}}": { + "transport": "streamable_http", + "url": url, + } + } + ) + {{/if}} +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MultiServerMCPClient: - """ - Returns an MCP Client compatible with LangChain/LangGraph. - """ + """Returns an MCP Client compatible with LangChain/LangGraph.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} return MultiServerMCPClient( { @@ -17,3 +80,4 @@ def get_streamable_http_mcp_client() -> MultiServerMCPClient: } } ) +{{/if}} diff --git a/src/assets/python/openaiagents/base/main.py b/src/assets/python/openaiagents/base/main.py index d75f6002..58c50f9c 100644 --- a/src/assets/python/openaiagents/base/main.py +++ b/src/assets/python/openaiagents/base/main.py @@ -30,12 +30,22 @@ def add_numbers(a: int, b: int) -> int: async def main(query): ensure_credentials_loaded() try: - async with mcp_server as server: - active_servers = [server] if server else [] + if mcp_server: + async with mcp_server as server: + active_servers = [server] + agent = Agent( + name="{{ name }}", + model="gpt-4.1", + mcp_servers=active_servers, + tools=[add_numbers] + ) + result = await Runner.run(agent, query) + return result + else: agent = Agent( name="{{ name }}", model="gpt-4.1", - mcp_servers=active_servers, + mcp_servers=[], tools=[add_numbers] ) result = await Runner.run(agent, query) diff --git a/src/assets/python/openaiagents/base/mcp_client/client.py b/src/assets/python/openaiagents/base/mcp_client/client.py index 9796d575..8b92389a 100644 --- a/src/assets/python/openaiagents/base/mcp_client/client.py +++ b/src/assets/python/openaiagents/base/mcp_client/client.py @@ -1,14 +1,67 @@ +import os +import logging from agents.mcp import MCPServerStreamableHttp +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +def get_streamable_http_mcp_client() -> MCPServerStreamableHttp | None: + """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) + return MCPServerStreamableHttp( + name="{{gatewayProviders.[0].name}}", params={"url": url, "http_client": http_client} + ) + {{else}} + return MCPServerStreamableHttp( + name="{{gatewayProviders.[0].name}}", params={"url": url} + ) + {{/if}} +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MCPServerStreamableHttp: - """ - Returns an MCP Client compatible with OpenAI Agents SDK. - """ + """Returns an MCP Client compatible with OpenAI Agents SDK.""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} return MCPServerStreamableHttp( name="AgentCore Gateway MCP", params={"url": EXAMPLE_MCP_ENDPOINT} ) +{{/if}} diff --git a/src/assets/python/strands/base/main.py b/src/assets/python/strands/base/main.py index a5557405..45021f40 100644 --- a/src/assets/python/strands/base/main.py +++ b/src/assets/python/strands/base/main.py @@ -22,6 +22,10 @@ def add_numbers(a: int, b: int) -> int: return a+b tools.append(add_numbers) +# Add MCP client to tools if available +if mcp_client: + tools.append(mcp_client) + {{#if hasMemory}} def agent_factory(): @@ -36,7 +40,7 @@ def get_or_create_agent(session_id, user_id): system_prompt=""" You are a helpful assistant. Use tools when appropriate. """, - tools=tools+[mcp_client] + tools=tools ) return cache[key] return get_or_create_agent @@ -52,7 +56,7 @@ def get_or_create_agent(): system_prompt=""" You are a helpful assistant. Use tools when appropriate. """, - tools=tools+[mcp_client] + tools=tools ) return _agent {{/if}} diff --git a/src/assets/python/strands/base/mcp_client/client.py b/src/assets/python/strands/base/mcp_client/client.py index cf292870..491b7b4f 100644 --- a/src/assets/python/strands/base/mcp_client/client.py +++ b/src/assets/python/strands/base/mcp_client/client.py @@ -1,12 +1,61 @@ +import os +import logging from mcp.client.streamable_http import streamablehttp_client from strands.tools.mcp.mcp_client import MCPClient +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +import boto3 +import httpx +from botocore.auth import SigV4Auth +from botocore.awsrequest import AWSRequest + + +class SigV4HTTPXAuth(httpx.Auth): + """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" + + def __init__(self): + session = boto3.Session() + credentials = session.get_credentials().get_frozen_credentials() + region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") + self.signer = SigV4Auth(credentials, "lambda", region) + + def auth_flow(self, request): + headers = dict(request.headers) + headers.pop("connection", None) + aws_request = AWSRequest( + method=request.method, + url=str(request.url), + data=request.content, + headers=headers, + ) + self.signer.add_auth(aws_request) + request.headers.update(dict(aws_request.headers)) + yield request +{{/if}} + + +def get_streamable_http_mcp_client() -> MCPClient | None: + """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" + url = os.environ.get("{{gatewayProviders.[0].envVarName}}") + if not url: + logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + return None + + {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) + return MCPClient(lambda: streamablehttp_client(url, http_client=http_client)) + {{else}} + return MCPClient(lambda: streamablehttp_client(url)) + {{/if}} +{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" def get_streamable_http_mcp_client() -> MCPClient: - """ - Returns an MCP Client compatible with Strands - """ + """Returns an MCP Client compatible with Strands""" # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} - return MCPClient(lambda: streamablehttp_client(EXAMPLE_MCP_ENDPOINT)) \ No newline at end of file + return MCPClient(lambda: streamablehttp_client(EXAMPLE_MCP_ENDPOINT)) +{{/if}} diff --git a/src/cli/commands/add/actions.ts b/src/cli/commands/add/actions.ts index 0d52fd3f..5394a685 100644 --- a/src/cli/commands/add/actions.ts +++ b/src/cli/commands/add/actions.ts @@ -160,7 +160,7 @@ async function handleCreatePath(options: ValidatedAddAgentOptions, configBaseDir } // Render templates with correct identity provider - const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, identityProviders); + const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders); const renderer = createRenderer(renderConfig); await renderer.render({ outputDir: projectRoot }); diff --git a/src/cli/commands/create/action.ts b/src/cli/commands/create/action.ts index da4d40c2..e6865ca2 100644 --- a/src/cli/commands/create/action.ts +++ b/src/cli/commands/create/action.ts @@ -197,7 +197,7 @@ export async function createProjectWithAgent(options: CreateWithAgentOptions): P } // Generate agent code with correct identity provider - const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, identityProviders); + const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders); const renderer = createRenderer(renderConfig); await renderer.render({ outputDir: projectRoot }); diff --git a/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts b/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts index 40faf584..593c9520 100644 --- a/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts +++ b/src/cli/operations/agent/generate/__tests__/schema-mapper.test.ts @@ -148,43 +148,45 @@ describe('mapModelProviderToIdentityProviders', () => { }); describe('mapGenerateConfigToRenderConfig', () => { - it('maps config with no memory and no identity', () => { - const result = mapGenerateConfigToRenderConfig(baseConfig, []); + it('maps config with no memory and no identity', async () => { + const result = await mapGenerateConfigToRenderConfig(baseConfig, []); expect(result.name).toBe('TestProject'); expect(result.sdkFramework).toBe('Strands'); expect(result.targetLanguage).toBe('Python'); expect(result.modelProvider).toBe('Bedrock'); expect(result.hasMemory).toBe(false); expect(result.hasIdentity).toBe(false); + expect(result.hasGateway).toBe(false); expect(result.memoryProviders).toEqual([]); expect(result.identityProviders).toEqual([]); + expect(result.gatewayProviders).toEqual([]); }); - it('sets hasMemory true when memory is not "none"', () => { + it('sets hasMemory true when memory is not "none"', async () => { const config: GenerateConfig = { ...baseConfig, memory: 'shortTerm' }; - const result = mapGenerateConfigToRenderConfig(config, []); + const result = await mapGenerateConfigToRenderConfig(config, []); expect(result.hasMemory).toBe(true); }); - it('sets hasIdentity true when identity providers exist', () => { + it('sets hasIdentity true when identity providers exist', async () => { const identityProviders = [{ name: 'ProjAnthropic', envVarName: 'AGENTCORE_CREDENTIAL_PROJANTHROPIC' }]; - const result = mapGenerateConfigToRenderConfig(baseConfig, identityProviders); + const result = await mapGenerateConfigToRenderConfig(baseConfig, identityProviders); expect(result.hasIdentity).toBe(true); expect(result.identityProviders).toEqual(identityProviders); }); - it('populates memoryProviders for shortTerm memory', () => { + it('populates memoryProviders for shortTerm memory', async () => { const config: GenerateConfig = { ...baseConfig, memory: 'shortTerm' }; - const result = mapGenerateConfigToRenderConfig(config, []); + const result = await mapGenerateConfigToRenderConfig(config, []); expect(result.memoryProviders).toHaveLength(1); expect(result.memoryProviders[0]!.name).toBe('TestProjectMemory'); expect(result.memoryProviders[0]!.envVarName).toBe('MEMORY_TESTPROJECTMEMORY_ID'); expect(result.memoryProviders[0]!.strategies).toEqual([]); }); - it('populates memoryProviders with strategy types for longAndShortTerm', () => { + it('populates memoryProviders with strategy types for longAndShortTerm', async () => { const config: GenerateConfig = { ...baseConfig, memory: 'longAndShortTerm' }; - const result = mapGenerateConfigToRenderConfig(config, []); + const result = await mapGenerateConfigToRenderConfig(config, []); expect(result.memoryProviders[0]!.strategies).toEqual(['SEMANTIC', 'USER_PREFERENCE', 'SUMMARIZATION']); }); }); diff --git a/src/cli/operations/agent/generate/schema-mapper.ts b/src/cli/operations/agent/generate/schema-mapper.ts index d4a6ae1b..97f996e2 100644 --- a/src/cli/operations/agent/generate/schema-mapper.ts +++ b/src/cli/operations/agent/generate/schema-mapper.ts @@ -1,4 +1,4 @@ -import { APP_DIR } from '../../../../lib'; +import { APP_DIR, ConfigIO } from '../../../../lib'; import type { AgentEnvSpec, Credential, @@ -12,6 +12,7 @@ import type { import { DEFAULT_STRATEGY_NAMESPACES } from '../../../../schema'; import type { AgentRenderConfig, + GatewayProviderRenderConfig, IdentityProviderRenderConfig, MemoryProviderRenderConfig, } from '../../../templates/types'; @@ -23,6 +24,7 @@ import { } from '../../../tui/screens/generate/defaults'; import type { GenerateConfig, MemoryOption } from '../../../tui/screens/generate/types'; import { computeDefaultCredentialEnvVarName } from '../../identity/create-identity'; +import { computeDefaultGatewayEnvVarName } from '../../mcp/create-mcp'; /** * Result of mapping GenerateConfig to v2 schema. @@ -176,15 +178,37 @@ export function mapModelProviderToIdentityProviders( ]; } +/** + * Maps MCP gateways to gateway providers for template rendering. + */ +async function mapMcpGatewaysToGatewayProviders(): Promise { + try { + const configIO = new ConfigIO(); + if (!configIO.configExists('mcp')) { + return []; + } + const mcpSpec = await configIO.readMcpSpec(); + return mcpSpec.agentCoreGateways.map(gateway => ({ + name: gateway.name, + envVarName: computeDefaultGatewayEnvVarName(gateway.name), + authType: gateway.authorizerType, + })); + } catch { + return []; + } +} + /** * Maps GenerateConfig to AgentRenderConfig for template rendering. * @param config - Generate config (note: config.projectName is actually the agent name) * @param identityProviders - Identity providers to include (caller controls credential naming) */ -export function mapGenerateConfigToRenderConfig( +export async function mapGenerateConfigToRenderConfig( config: GenerateConfig, identityProviders: IdentityProviderRenderConfig[] -): AgentRenderConfig { +): Promise { + const gatewayProviders = await mapMcpGatewaysToGatewayProviders(); + return { name: config.projectName, sdkFramework: config.sdk, @@ -192,8 +216,10 @@ export function mapGenerateConfigToRenderConfig( modelProvider: config.modelProvider, hasMemory: config.memory !== 'none', hasIdentity: identityProviders.length > 0, + hasGateway: gatewayProviders.length > 0, buildType: config.buildType, memoryProviders: mapMemoryOptionToMemoryProviders(config.memory, config.projectName), identityProviders, + gatewayProviders, }; } diff --git a/src/cli/templates/types.ts b/src/cli/templates/types.ts index 37dded4e..033b5639 100644 --- a/src/cli/templates/types.ts +++ b/src/cli/templates/types.ts @@ -18,6 +18,15 @@ export interface MemoryProviderRenderConfig { strategies: MemoryStrategyType[]; } +/** + * Gateway provider info for template rendering. + */ +export interface GatewayProviderRenderConfig { + name: string; + envVarName: string; + authType: string; // AWS_IAM, CUSTOM_JWT, NONE +} + /** * Configuration needed by template renderers. * This is separate from the v2 Agent schema which only stores runtime config. @@ -29,10 +38,13 @@ export interface AgentRenderConfig { modelProvider: ModelProvider; hasMemory: boolean; hasIdentity: boolean; + hasGateway: boolean; /** Build type: CodeZip (default) or Container */ buildType?: BuildType; /** Memory providers for template rendering */ memoryProviders: MemoryProviderRenderConfig[]; /** Identity providers for template rendering (maps to credentials in schema) */ identityProviders: IdentityProviderRenderConfig[]; + /** Gateway providers for template rendering */ + gatewayProviders: GatewayProviderRenderConfig[]; } diff --git a/src/cli/tui/screens/agent/useAddAgent.ts b/src/cli/tui/screens/agent/useAddAgent.ts index c5830385..cf6b89a6 100644 --- a/src/cli/tui/screens/agent/useAddAgent.ts +++ b/src/cli/tui/screens/agent/useAddAgent.ts @@ -170,7 +170,7 @@ async function handleCreatePath( } // Generate agent files with correct identity provider - const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, identityProviders); + const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders); const renderer = createRenderer(renderConfig); await renderer.render({ outputDir: projectRoot }); diff --git a/src/cli/tui/screens/create/useCreateFlow.ts b/src/cli/tui/screens/create/useCreateFlow.ts index 324b9e3c..079b8fc6 100644 --- a/src/cli/tui/screens/create/useCreateFlow.ts +++ b/src/cli/tui/screens/create/useCreateFlow.ts @@ -301,7 +301,7 @@ export function useCreateFlow(cwd: string): CreateFlowState { } // Render with correct identity provider - const renderConfig = mapGenerateConfigToRenderConfig(generateConfig, identityProviders); + const renderConfig = await mapGenerateConfigToRenderConfig(generateConfig, identityProviders); const renderer = createRenderer(renderConfig); logger.logSubStep('Rendering agent template...'); await renderer.render({ outputDir: projectRoot }); diff --git a/src/cli/tui/screens/generate/useGenerateFlow.ts b/src/cli/tui/screens/generate/useGenerateFlow.ts index 3c44f9d4..60a39eab 100644 --- a/src/cli/tui/screens/generate/useGenerateFlow.ts +++ b/src/cli/tui/screens/generate/useGenerateFlow.ts @@ -74,7 +74,7 @@ export function useGenerateFlow(): GenerateFlowState { // Build identity providers for template rendering const identityProviders = mapModelProviderToIdentityProviders(config.modelProvider, projectSpec.name); - const renderConfig = mapGenerateConfigToRenderConfig(config, identityProviders); + const renderConfig = await mapGenerateConfigToRenderConfig(config, identityProviders); const renderer = createRenderer(renderConfig); await renderer.render({ outputDir: project.projectRoot }); await writeAgentToProject(config); From faca1a508a3f3fe8421cb56ed1348ec5be48033f Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 16:25:50 -0500 Subject: [PATCH 2/6] feat: add multi-gateway support and fix template rendering Replace single-gateway [0] indexing with {{#each gatewayProviders}} loops. Each gateway gets its own client function (Strands) or entry in the servers dict (LangChain/OpenAI/AutoGen/ADK). Add snakeCase Handlebars helper for gateway function names. Add gatewayAuthTypes array for conditional SigV4 imports. Fix @index parse error by using plain variable names. --- .../assets.snapshot.test.ts.snap | 209 +++++++----------- .../python/autogen/base/mcp_client/client.py | 52 +---- .../googleadk/base/mcp_client/client.py | 52 +---- .../base/mcp_client/client.py | 45 ++-- .../openaiagents/base/mcp_client/client.py | 34 ++- .../python/strands/base/mcp_client/client.py | 26 ++- .../agent/generate/schema-mapper.ts | 1 + src/cli/templates/render.ts | 3 + src/cli/templates/types.ts | 2 + 9 files changed, 154 insertions(+), 270 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index da161d8d..bce7cfff 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -988,46 +988,18 @@ from autogen_ext.tools.mcp import ( logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request -{{/if}} - - -async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """Returns MCP Tools from the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") - return [] - - server_params = StreamableHttpServerParams(url=url) - return await mcp_server_tools(server_params) +async def get_all_gateway_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: + """Returns MCP Tools from all configured gateways.""" + tools = [] + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + server_params = StreamableHttpServerParams(url=url) + tools.extend(await mcp_server_tools(server_params)) + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + return tools {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" @@ -1747,47 +1719,17 @@ from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnecti logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request -{{/if}} - - -def get_streamable_http_mcp_client() -> MCPToolset | None: - """Returns an MCP Toolset connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") - return None - - return MCPToolset( - connection_params=StreamableHTTPConnectionParams(url=url) - ) +def get_all_gateway_mcp_toolsets() -> list[MCPToolset]: + """Returns MCP Toolsets for all configured gateways.""" + toolsets = [] + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + toolsets.append(MCPToolset(connection_params=StreamableHTTPConnectionParams(url=url))) + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + return toolsets {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" @@ -2040,7 +1982,7 @@ from langchain_mcp_adapters.client import MultiServerMCPClient logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} import boto3 import httpx from botocore.auth import SigV4Auth @@ -2071,34 +2013,23 @@ class SigV4HTTPXAuth(httpx.Auth): {{/if}} -def get_streamable_http_mcp_client() -> MultiServerMCPClient | None: - """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") +def get_all_gateway_mcp_client() -> MultiServerMCPClient | None: + """Returns an MCP Client connected to all configured gateways.""" + servers = {} + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + {{#if (eq authType "AWS_IAM")}} + servers["{{name}}"] = {"transport": "streamable_http", "url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())} + {{else}} + servers["{{name}}"] = {"transport": "streamable_http", "url": url} + {{/if}} + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + if not servers: return None - - {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} - http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) - return MultiServerMCPClient( - { - "{{gatewayProviders.[0].name}}": { - "transport": "streamable_http", - "url": url, - "http_client": http_client, - } - } - ) - {{else}} - return MultiServerMCPClient( - { - "{{gatewayProviders.[0].name}}": { - "transport": "streamable_http", - "url": url, - } - } - ) - {{/if}} + return MultiServerMCPClient(servers) {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" @@ -2469,7 +2400,7 @@ from agents.mcp import MCPServerStreamableHttp logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} import boto3 import httpx from botocore.auth import SigV4Auth @@ -2500,23 +2431,21 @@ class SigV4HTTPXAuth(httpx.Auth): {{/if}} -def get_streamable_http_mcp_client() -> MCPServerStreamableHttp | None: - """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") - return None - - {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} - http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) - return MCPServerStreamableHttp( - name="{{gatewayProviders.[0].name}}", params={"url": url, "http_client": http_client} - ) - {{else}} - return MCPServerStreamableHttp( - name="{{gatewayProviders.[0].name}}", params={"url": url} - ) - {{/if}} +def get_all_gateway_mcp_servers() -> list[MCPServerStreamableHttp]: + """Returns MCP servers for all configured gateways.""" + servers = [] + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + {{#if (eq authType "AWS_IAM")}} + servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())})) + {{else}} + servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url})) + {{/if}} + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + return servers {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" @@ -2793,7 +2722,7 @@ from strands.tools.mcp.mcp_client import MCPClient logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} import boto3 import httpx from botocore.auth import SigV4Auth @@ -2823,20 +2752,30 @@ class SigV4HTTPXAuth(httpx.Auth): yield request {{/if}} - -def get_streamable_http_mcp_client() -> MCPClient | None: - """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") +{{#each gatewayProviders}} +def get_{{snakeCase name}}_mcp_client() -> MCPClient | None: + """Returns an MCP Client connected to the {{name}} gateway.""" + url = os.environ.get("{{envVarName}}") if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") return None - - {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + {{#if (eq authType "AWS_IAM")}} http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) return MCPClient(lambda: streamablehttp_client(url, http_client=http_client)) {{else}} return MCPClient(lambda: streamablehttp_client(url)) {{/if}} + +{{/each}} +def get_all_gateway_mcp_clients() -> list[MCPClient]: + """Returns MCP clients for all configured gateways.""" + clients = [] + {{#each gatewayProviders}} + client = get_{{snakeCase name}}_mcp_client() + if client: + clients.append(client) + {{/each}} + return clients {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" diff --git a/src/assets/python/autogen/base/mcp_client/client.py b/src/assets/python/autogen/base/mcp_client/client.py index 94d184cb..3f69098d 100644 --- a/src/assets/python/autogen/base/mcp_client/client.py +++ b/src/assets/python/autogen/base/mcp_client/client.py @@ -10,46 +10,18 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request -{{/if}} - - -async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """Returns MCP Tools from the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") - return [] - - server_params = StreamableHttpServerParams(url=url) - return await mcp_server_tools(server_params) +async def get_all_gateway_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: + """Returns MCP Tools from all configured gateways.""" + tools = [] + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + server_params = StreamableHttpServerParams(url=url) + tools.extend(await mcp_server_tools(server_params)) + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + return tools {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" diff --git a/src/assets/python/googleadk/base/mcp_client/client.py b/src/assets/python/googleadk/base/mcp_client/client.py index 9a344b08..91f86c78 100644 --- a/src/assets/python/googleadk/base/mcp_client/client.py +++ b/src/assets/python/googleadk/base/mcp_client/client.py @@ -6,47 +6,17 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request -{{/if}} - - -def get_streamable_http_mcp_client() -> MCPToolset | None: - """Returns an MCP Toolset connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") - return None - - return MCPToolset( - connection_params=StreamableHTTPConnectionParams(url=url) - ) +def get_all_gateway_mcp_toolsets() -> list[MCPToolset]: + """Returns MCP Toolsets for all configured gateways.""" + toolsets = [] + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + toolsets.append(MCPToolset(connection_params=StreamableHTTPConnectionParams(url=url))) + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + return toolsets {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" diff --git a/src/assets/python/langchain_langgraph/base/mcp_client/client.py b/src/assets/python/langchain_langgraph/base/mcp_client/client.py index 3d2a3260..484097ac 100644 --- a/src/assets/python/langchain_langgraph/base/mcp_client/client.py +++ b/src/assets/python/langchain_langgraph/base/mcp_client/client.py @@ -5,7 +5,7 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} import boto3 import httpx from botocore.auth import SigV4Auth @@ -36,34 +36,23 @@ def auth_flow(self, request): {{/if}} -def get_streamable_http_mcp_client() -> MultiServerMCPClient | None: - """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") +def get_all_gateway_mcp_client() -> MultiServerMCPClient | None: + """Returns an MCP Client connected to all configured gateways.""" + servers = {} + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + {{#if (eq authType "AWS_IAM")}} + servers["{{name}}"] = {"transport": "streamable_http", "url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())} + {{else}} + servers["{{name}}"] = {"transport": "streamable_http", "url": url} + {{/if}} + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + if not servers: return None - - {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} - http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) - return MultiServerMCPClient( - { - "{{gatewayProviders.[0].name}}": { - "transport": "streamable_http", - "url": url, - "http_client": http_client, - } - } - ) - {{else}} - return MultiServerMCPClient( - { - "{{gatewayProviders.[0].name}}": { - "transport": "streamable_http", - "url": url, - } - } - ) - {{/if}} + return MultiServerMCPClient(servers) {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" diff --git a/src/assets/python/openaiagents/base/mcp_client/client.py b/src/assets/python/openaiagents/base/mcp_client/client.py index 8b92389a..23ba7eeb 100644 --- a/src/assets/python/openaiagents/base/mcp_client/client.py +++ b/src/assets/python/openaiagents/base/mcp_client/client.py @@ -5,7 +5,7 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} import boto3 import httpx from botocore.auth import SigV4Auth @@ -36,23 +36,21 @@ def auth_flow(self, request): {{/if}} -def get_streamable_http_mcp_client() -> MCPServerStreamableHttp | None: - """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") - if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") - return None - - {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} - http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) - return MCPServerStreamableHttp( - name="{{gatewayProviders.[0].name}}", params={"url": url, "http_client": http_client} - ) - {{else}} - return MCPServerStreamableHttp( - name="{{gatewayProviders.[0].name}}", params={"url": url} - ) - {{/if}} +def get_all_gateway_mcp_servers() -> list[MCPServerStreamableHttp]: + """Returns MCP servers for all configured gateways.""" + servers = [] + {{#each gatewayProviders}} + url = os.environ.get("{{envVarName}}") + if url: + {{#if (eq authType "AWS_IAM")}} + servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())})) + {{else}} + servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url})) + {{/if}} + else: + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") + {{/each}} + return servers {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" diff --git a/src/assets/python/strands/base/mcp_client/client.py b/src/assets/python/strands/base/mcp_client/client.py index 491b7b4f..157029cb 100644 --- a/src/assets/python/strands/base/mcp_client/client.py +++ b/src/assets/python/strands/base/mcp_client/client.py @@ -6,7 +6,7 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} -{{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} import boto3 import httpx from botocore.auth import SigV4Auth @@ -36,20 +36,30 @@ def auth_flow(self, request): yield request {{/if}} - -def get_streamable_http_mcp_client() -> MCPClient | None: - """Returns an MCP Client connected to the {{gatewayProviders.[0].name}} gateway.""" - url = os.environ.get("{{gatewayProviders.[0].envVarName}}") +{{#each gatewayProviders}} +def get_{{snakeCase name}}_mcp_client() -> MCPClient | None: + """Returns an MCP Client connected to the {{name}} gateway.""" + url = os.environ.get("{{envVarName}}") if not url: - logger.warning("{{gatewayProviders.[0].envVarName}} not set — gateway tools unavailable") + logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") return None - - {{#if (eq gatewayProviders.[0].authType "AWS_IAM")}} + {{#if (eq authType "AWS_IAM")}} http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) return MCPClient(lambda: streamablehttp_client(url, http_client=http_client)) {{else}} return MCPClient(lambda: streamablehttp_client(url)) {{/if}} + +{{/each}} +def get_all_gateway_mcp_clients() -> list[MCPClient]: + """Returns MCP clients for all configured gateways.""" + clients = [] + {{#each gatewayProviders}} + client = get_{{snakeCase name}}_mcp_client() + if client: + clients.append(client) + {{/each}} + return clients {{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" diff --git a/src/cli/operations/agent/generate/schema-mapper.ts b/src/cli/operations/agent/generate/schema-mapper.ts index 97f996e2..3f9677ec 100644 --- a/src/cli/operations/agent/generate/schema-mapper.ts +++ b/src/cli/operations/agent/generate/schema-mapper.ts @@ -221,5 +221,6 @@ export async function mapGenerateConfigToRenderConfig( memoryProviders: mapMemoryOptionToMemoryProviders(config.memory, config.projectName), identityProviders, gatewayProviders, + gatewayAuthTypes: [...new Set(gatewayProviders.map(g => g.authType))], }; } diff --git a/src/cli/templates/render.ts b/src/cli/templates/render.ts index 166c90a3..09b2aa9e 100644 --- a/src/cli/templates/render.ts +++ b/src/cli/templates/render.ts @@ -8,6 +8,9 @@ Handlebars.registerHelper('includes', (array: unknown[], value: unknown) => { if (!Array.isArray(array)) return false; return array.includes(value); }); +Handlebars.registerHelper('snakeCase', (str: string) => { + return str.replace(/[- ]/g, '_').toLowerCase(); +}); /** * Renames template files to their actual names. diff --git a/src/cli/templates/types.ts b/src/cli/templates/types.ts index 033b5639..dbde6651 100644 --- a/src/cli/templates/types.ts +++ b/src/cli/templates/types.ts @@ -47,4 +47,6 @@ export interface AgentRenderConfig { identityProviders: IdentityProviderRenderConfig[]; /** Gateway providers for template rendering */ gatewayProviders: GatewayProviderRenderConfig[]; + /** Unique auth types across all gateways (for conditional imports) */ + gatewayAuthTypes: string[]; } From 1e3f58a85e667ea0c980ef3781e8bd0b28d33a09 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 16:34:54 -0500 Subject: [PATCH 3/6] fix: update main.py files with gateway conditional imports All 5 framework main.py files now use Handlebars conditionals to import the correct MCP client function based on hasGateway flag. Fix snakeCase helper to handle all special characters. --- .../assets.snapshot.test.ts.snap | 73 +++++++++++++++++-- src/assets/python/autogen/base/main.py | 8 ++ src/assets/python/googleadk/base/main.py | 8 ++ .../python/langchain_langgraph/base/main.py | 8 ++ src/assets/python/openaiagents/base/main.py | 34 ++++++++- src/assets/python/strands/base/main.py | 15 +++- src/cli/templates/render.ts | 2 +- 7 files changed, 137 insertions(+), 11 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index bce7cfff..b77ec30e 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -918,7 +918,11 @@ from autogen_agentchat.agents import AssistantAgent from autogen_core.tools import FunctionTool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_tools +{{else}} from mcp_client.client import get_streamable_http_mcp_tools +{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -943,7 +947,11 @@ async def invoke(payload, context): log.info("Invoking Agent.....") # Get MCP Tools + {{#if hasGateway}} + mcp_tools = await get_all_gateway_mcp_tools() + {{else}} mcp_tools = await get_streamable_http_mcp_tools() + {{/if}} if mcp_tools is None: mcp_tools = [] @@ -1617,7 +1625,11 @@ from google.adk.sessions import InMemorySessionService from google.genai import types from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_toolsets +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -1635,8 +1647,12 @@ def add_numbers(a: int, b: int) -> int: # Get MCP Toolset +{{#if hasGateway}} +mcp_toolset = get_all_gateway_mcp_toolsets() +{{else}} mcp_client = get_streamable_http_mcp_client() mcp_toolset = [mcp_client] if mcp_client else [] +{{/if}} _credentials_loaded = False @@ -1914,7 +1930,11 @@ from langgraph.prebuilt import create_react_agent from langchain.tools import tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_client +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -1944,7 +1964,11 @@ async def invoke(payload, context): log.info("Invoking Agent.....") # Get MCP Client + {{#if hasGateway}} + mcp_client = get_all_gateway_mcp_client() + {{else}} mcp_client = get_streamable_http_mcp_client() + {{/if}} # Load MCP Tools mcp_tools = [] @@ -2315,13 +2339,22 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/op from agents import Agent, Runner, function_tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_servers +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} app = BedrockAgentCoreApp() log = app.logger # Get MCP Server +{{#if hasGateway}} +mcp_servers = get_all_gateway_mcp_servers() +{{else}} mcp_server = get_streamable_http_mcp_client() +mcp_servers = [mcp_server] if mcp_server else [] +{{/if}} _credentials_loaded = False @@ -2343,8 +2376,28 @@ def add_numbers(a: int, b: int) -> int: async def main(query): ensure_credentials_loaded() try: - if mcp_server: - async with mcp_server as server: + {{#if hasGateway}} + if mcp_servers: + agent = Agent( + name="{{ name }}", + model="gpt-4.1", + mcp_servers=mcp_servers, + tools=[add_numbers] + ) + result = await Runner.run(agent, query) + return result + else: + agent = Agent( + name="{{ name }}", + model="gpt-4.1", + mcp_servers=[], + tools=[add_numbers] + ) + result = await Runner.run(agent, query) + return result + {{else}} + if mcp_servers: + async with mcp_servers[0] as server: active_servers = [server] agent = Agent( name="{{ name }}", @@ -2363,6 +2416,7 @@ async def main(query): ) result = await Runner.run(agent, query) return result + {{/if}} except Exception as e: log.error(f"Error during agent execution: {e}", exc_info=True) raise e @@ -2621,7 +2675,11 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/st "from strands import Agent, tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_clients +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} {{#if hasMemory}} from memory.session import get_memory_session_manager {{/if}} @@ -2630,7 +2688,11 @@ app = BedrockAgentCoreApp() log = app.logger # Define a Streamable HTTP MCP Client -mcp_client = get_streamable_http_mcp_client() +{{#if hasGateway}} +mcp_clients = get_all_gateway_mcp_clients() +{{else}} +mcp_clients = [get_streamable_http_mcp_client()] +{{/if}} # Define a collection of tools used by the model tools = [] @@ -2643,8 +2705,9 @@ def add_numbers(a: int, b: int) -> int: tools.append(add_numbers) # Add MCP client to tools if available -if mcp_client: - tools.append(mcp_client) +for mcp_client in mcp_clients: + if mcp_client: + tools.append(mcp_client) {{#if hasMemory}} diff --git a/src/assets/python/autogen/base/main.py b/src/assets/python/autogen/base/main.py index 2eb4dbba..a279dbce 100644 --- a/src/assets/python/autogen/base/main.py +++ b/src/assets/python/autogen/base/main.py @@ -3,7 +3,11 @@ from autogen_core.tools import FunctionTool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_tools +{{else}} from mcp_client.client import get_streamable_http_mcp_tools +{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -28,7 +32,11 @@ async def invoke(payload, context): log.info("Invoking Agent.....") # Get MCP Tools + {{#if hasGateway}} + mcp_tools = await get_all_gateway_mcp_tools() + {{else}} mcp_tools = await get_streamable_http_mcp_tools() + {{/if}} if mcp_tools is None: mcp_tools = [] diff --git a/src/assets/python/googleadk/base/main.py b/src/assets/python/googleadk/base/main.py index f830b873..5ce99608 100644 --- a/src/assets/python/googleadk/base/main.py +++ b/src/assets/python/googleadk/base/main.py @@ -5,7 +5,11 @@ from google.genai import types from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_toolsets +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -23,8 +27,12 @@ def add_numbers(a: int, b: int) -> int: # Get MCP Toolset +{{#if hasGateway}} +mcp_toolset = get_all_gateway_mcp_toolsets() +{{else}} mcp_client = get_streamable_http_mcp_client() mcp_toolset = [mcp_client] if mcp_client else [] +{{/if}} _credentials_loaded = False diff --git a/src/assets/python/langchain_langgraph/base/main.py b/src/assets/python/langchain_langgraph/base/main.py index 1447a3e7..3047d124 100644 --- a/src/assets/python/langchain_langgraph/base/main.py +++ b/src/assets/python/langchain_langgraph/base/main.py @@ -4,7 +4,11 @@ from langchain.tools import tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_client +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -34,7 +38,11 @@ async def invoke(payload, context): log.info("Invoking Agent.....") # Get MCP Client + {{#if hasGateway}} + mcp_client = get_all_gateway_mcp_client() + {{else}} mcp_client = get_streamable_http_mcp_client() + {{/if}} # Load MCP Tools mcp_tools = [] diff --git a/src/assets/python/openaiagents/base/main.py b/src/assets/python/openaiagents/base/main.py index 58c50f9c..57f49755 100644 --- a/src/assets/python/openaiagents/base/main.py +++ b/src/assets/python/openaiagents/base/main.py @@ -2,13 +2,22 @@ from agents import Agent, Runner, function_tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_servers +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} app = BedrockAgentCoreApp() log = app.logger # Get MCP Server +{{#if hasGateway}} +mcp_servers = get_all_gateway_mcp_servers() +{{else}} mcp_server = get_streamable_http_mcp_client() +mcp_servers = [mcp_server] if mcp_server else [] +{{/if}} _credentials_loaded = False @@ -30,8 +39,28 @@ def add_numbers(a: int, b: int) -> int: async def main(query): ensure_credentials_loaded() try: - if mcp_server: - async with mcp_server as server: + {{#if hasGateway}} + if mcp_servers: + agent = Agent( + name="{{ name }}", + model="gpt-4.1", + mcp_servers=mcp_servers, + tools=[add_numbers] + ) + result = await Runner.run(agent, query) + return result + else: + agent = Agent( + name="{{ name }}", + model="gpt-4.1", + mcp_servers=[], + tools=[add_numbers] + ) + result = await Runner.run(agent, query) + return result + {{else}} + if mcp_servers: + async with mcp_servers[0] as server: active_servers = [server] agent = Agent( name="{{ name }}", @@ -50,6 +79,7 @@ async def main(query): ) result = await Runner.run(agent, query) return result + {{/if}} except Exception as e: log.error(f"Error during agent execution: {e}", exc_info=True) raise e diff --git a/src/assets/python/strands/base/main.py b/src/assets/python/strands/base/main.py index 45021f40..21546915 100644 --- a/src/assets/python/strands/base/main.py +++ b/src/assets/python/strands/base/main.py @@ -1,7 +1,11 @@ from strands import Agent, tool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model +{{#if hasGateway}} +from mcp_client.client import get_all_gateway_mcp_clients +{{else}} from mcp_client.client import get_streamable_http_mcp_client +{{/if}} {{#if hasMemory}} from memory.session import get_memory_session_manager {{/if}} @@ -10,7 +14,11 @@ log = app.logger # Define a Streamable HTTP MCP Client -mcp_client = get_streamable_http_mcp_client() +{{#if hasGateway}} +mcp_clients = get_all_gateway_mcp_clients() +{{else}} +mcp_clients = [get_streamable_http_mcp_client()] +{{/if}} # Define a collection of tools used by the model tools = [] @@ -23,8 +31,9 @@ def add_numbers(a: int, b: int) -> int: tools.append(add_numbers) # Add MCP client to tools if available -if mcp_client: - tools.append(mcp_client) +for mcp_client in mcp_clients: + if mcp_client: + tools.append(mcp_client) {{#if hasMemory}} diff --git a/src/cli/templates/render.ts b/src/cli/templates/render.ts index 09b2aa9e..e56a8490 100644 --- a/src/cli/templates/render.ts +++ b/src/cli/templates/render.ts @@ -9,7 +9,7 @@ Handlebars.registerHelper('includes', (array: unknown[], value: unknown) => { return array.includes(value); }); Handlebars.registerHelper('snakeCase', (str: string) => { - return str.replace(/[- ]/g, '_').toLowerCase(); + return str.replace(/[^a-zA-Z0-9]/g, '_').toLowerCase(); }); /** From 9dfe27b2bdc7bca41ec55cfc632eb9368ac5f866 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 16:35:58 -0500 Subject: [PATCH 4/6] style: fix formatting --- src/cli/operations/agent/generate/schema-mapper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cli/operations/agent/generate/schema-mapper.ts b/src/cli/operations/agent/generate/schema-mapper.ts index 3f9677ec..996fab70 100644 --- a/src/cli/operations/agent/generate/schema-mapper.ts +++ b/src/cli/operations/agent/generate/schema-mapper.ts @@ -208,7 +208,7 @@ export async function mapGenerateConfigToRenderConfig( identityProviders: IdentityProviderRenderConfig[] ): Promise { const gatewayProviders = await mapMcpGatewaysToGatewayProviders(); - + return { name: config.projectName, sdkFramework: config.sdk, From 4cb8d1ccba2c80259e1d9526afc430ac818951d5 Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Tue, 24 Feb 2026 21:46:55 -0500 Subject: [PATCH 5/6] refactor: use mcp-proxy-for-aws for gateway auth, remove AutoGen gateway support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace custom SigV4HTTPXAuth class with official mcp-proxy-for-aws package: - Strands: aws_iam_streamablehttp_client factory pattern - LangChain: SigV4HTTPXAuth via auth param in MultiServerMCPClient config - OpenAI Agents: SigV4HTTPXAuth via httpx_client_factory param - Google ADK: SigV4HTTPXAuth via httpx_client_factory in StreamableHTTPConnectionParams Revert AutoGen to original upstream — SDK doesn't support custom httpx auth (no httpx_client_factory param). --- .../assets.snapshot.test.ts.snap | 156 +++++------------- src/assets/python/autogen/base/main.py | 10 -- .../python/autogen/base/mcp_client/client.py | 23 +-- .../googleadk/base/mcp_client/client.py | 14 ++ .../python/googleadk/base/pyproject.toml | 2 + .../base/mcp_client/client.py | 33 +--- .../langchain_langgraph/base/pyproject.toml | 2 + .../openaiagents/base/mcp_client/client.py | 35 +--- .../python/openaiagents/base/pyproject.toml | 2 + .../python/strands/base/mcp_client/client.py | 31 +--- src/assets/python/strands/base/pyproject.toml | 2 + 11 files changed, 77 insertions(+), 233 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index b77ec30e..d77491b0 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -918,11 +918,7 @@ from autogen_agentchat.agents import AssistantAgent from autogen_core.tools import FunctionTool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model -{{#if hasGateway}} -from mcp_client.client import get_all_gateway_mcp_tools -{{else}} from mcp_client.client import get_streamable_http_mcp_tools -{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -947,13 +943,7 @@ async def invoke(payload, context): log.info("Invoking Agent.....") # Get MCP Tools - {{#if hasGateway}} - mcp_tools = await get_all_gateway_mcp_tools() - {{else}} mcp_tools = await get_streamable_http_mcp_tools() - {{/if}} - if mcp_tools is None: - mcp_tools = [] # Define an AssistantAgent with the model and tools agent = AssistantAgent( @@ -984,41 +974,24 @@ exports[`Assets Directory Snapshots > Python framework assets > python/python/au `; exports[`Assets Directory Snapshots > Python framework assets > python/python/autogen/base/mcp_client/client.py should match snapshot 1`] = ` -"import os -import logging -from typing import List +"from typing import List from autogen_ext.tools.mcp import ( StreamableHttpMcpToolAdapter, StreamableHttpServerParams, mcp_server_tools, ) -logger = logging.getLogger(__name__) - -{{#if hasGateway}} -async def get_all_gateway_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """Returns MCP Tools from all configured gateways.""" - tools = [] - {{#each gatewayProviders}} - url = os.environ.get("{{envVarName}}") - if url: - server_params = StreamableHttpServerParams(url=url) - tools.extend(await mcp_server_tools(server_params)) - else: - logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") - {{/each}} - return tools -{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """Returns MCP Tools compatible with AutoGen.""" + """ + Returns MCP Tools compatible with AutoGen. + """ # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} server_params = StreamableHttpServerParams(url=EXAMPLE_MCP_ENDPOINT) return await mcp_server_tools(server_params) -{{/if}} " `; @@ -1735,13 +1708,27 @@ from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnecti logger = logging.getLogger(__name__) {{#if hasGateway}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} +import httpx +from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth, create_aws_session +{{/if}} + def get_all_gateway_mcp_toolsets() -> list[MCPToolset]: """Returns MCP Toolsets for all configured gateways.""" toolsets = [] {{#each gatewayProviders}} url = os.environ.get("{{envVarName}}") if url: + {{#if (eq authType "AWS_IAM")}} + session = create_aws_session() + auth = SigV4HTTPXAuth(session.get_credentials(), "bedrock-agentcore", session.region_name) + toolsets.append(MCPToolset(connection_params=StreamableHTTPConnectionParams( + url=url, + httpx_client_factory=lambda **kwargs: httpx.AsyncClient(auth=auth, **kwargs) + ))) + {{else}} toolsets.append(MCPToolset(connection_params=StreamableHTTPConnectionParams(url=url))) + {{/if}} else: logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") {{/each}} @@ -1828,6 +1815,8 @@ dependencies = [ "google-adk >= 1.17.0", "bedrock-agentcore >= 1.0.3", "botocore[crt] >= 1.35.0", + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] @@ -2007,36 +1996,9 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} {{#if (includes gatewayAuthTypes "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request +from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth, create_aws_session {{/if}} - def get_all_gateway_mcp_client() -> MultiServerMCPClient | None: """Returns an MCP Client connected to all configured gateways.""" servers = {} @@ -2044,7 +2006,9 @@ def get_all_gateway_mcp_client() -> MultiServerMCPClient | None: url = os.environ.get("{{envVarName}}") if url: {{#if (eq authType "AWS_IAM")}} - servers["{{name}}"] = {"transport": "streamable_http", "url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())} + session = create_aws_session() + auth = SigV4HTTPXAuth(session.get_credentials(), "bedrock-agentcore", session.region_name) + servers["{{name}}"] = {"transport": "streamable_http", "url": url, "auth": auth} {{else}} servers["{{name}}"] = {"transport": "streamable_http", "url": url} {{/if}} @@ -2239,6 +2203,8 @@ dependencies = [ {{#if (eq modelProvider "Gemini")}} "langchain-google-genai >= 3.0.3", {{/if}} + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] @@ -2455,36 +2421,10 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} {{#if (includes gatewayAuthTypes "AWS_IAM")}} -import boto3 import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request +from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth, create_aws_session {{/if}} - def get_all_gateway_mcp_servers() -> list[MCPServerStreamableHttp]: """Returns MCP servers for all configured gateways.""" servers = [] @@ -2492,7 +2432,12 @@ def get_all_gateway_mcp_servers() -> list[MCPServerStreamableHttp]: url = os.environ.get("{{envVarName}}") if url: {{#if (eq authType "AWS_IAM")}} - servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())})) + session = create_aws_session() + auth = SigV4HTTPXAuth(session.get_credentials(), "bedrock-agentcore", session.region_name) + servers.append(MCPServerStreamableHttp( + name="{{name}}", + params={"url": url, "httpx_client_factory": lambda **kwargs: httpx.AsyncClient(auth=auth, **kwargs)} + )) {{else}} servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url})) {{/if}} @@ -2577,6 +2522,8 @@ dependencies = [ "openai-agents >= 0.4.2", "bedrock-agentcore >= 1.0.3", "botocore[crt] >= 1.35.0", + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] @@ -2786,33 +2733,7 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} {{#if (includes gatewayAuthTypes "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request +from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client {{/if}} {{#each gatewayProviders}} @@ -2823,8 +2744,7 @@ def get_{{snakeCase name}}_mcp_client() -> MCPClient | None: logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") return None {{#if (eq authType "AWS_IAM")}} - http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) - return MCPClient(lambda: streamablehttp_client(url, http_client=http_client)) + return MCPClient(lambda: aws_iam_streamablehttp_client(url, aws_service="bedrock-agentcore")) {{else}} return MCPClient(lambda: streamablehttp_client(url)) {{/if}} @@ -3003,6 +2923,8 @@ dependencies = [ {{/if}}"mcp >= 1.19.0", {{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0", {{/if}}"strands-agents >= 1.13.0", + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] diff --git a/src/assets/python/autogen/base/main.py b/src/assets/python/autogen/base/main.py index a279dbce..43789e63 100644 --- a/src/assets/python/autogen/base/main.py +++ b/src/assets/python/autogen/base/main.py @@ -3,11 +3,7 @@ from autogen_core.tools import FunctionTool from bedrock_agentcore.runtime import BedrockAgentCoreApp from model.load import load_model -{{#if hasGateway}} -from mcp_client.client import get_all_gateway_mcp_tools -{{else}} from mcp_client.client import get_streamable_http_mcp_tools -{{/if}} app = BedrockAgentCoreApp() log = app.logger @@ -32,13 +28,7 @@ async def invoke(payload, context): log.info("Invoking Agent.....") # Get MCP Tools - {{#if hasGateway}} - mcp_tools = await get_all_gateway_mcp_tools() - {{else}} mcp_tools = await get_streamable_http_mcp_tools() - {{/if}} - if mcp_tools is None: - mcp_tools = [] # Define an AssistantAgent with the model and tools agent = AssistantAgent( diff --git a/src/assets/python/autogen/base/mcp_client/client.py b/src/assets/python/autogen/base/mcp_client/client.py index 3f69098d..7f4c5c3b 100644 --- a/src/assets/python/autogen/base/mcp_client/client.py +++ b/src/assets/python/autogen/base/mcp_client/client.py @@ -1,5 +1,3 @@ -import os -import logging from typing import List from autogen_ext.tools.mcp import ( StreamableHttpMcpToolAdapter, @@ -7,29 +5,14 @@ mcp_server_tools, ) -logger = logging.getLogger(__name__) - -{{#if hasGateway}} -async def get_all_gateway_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """Returns MCP Tools from all configured gateways.""" - tools = [] - {{#each gatewayProviders}} - url = os.environ.get("{{envVarName}}") - if url: - server_params = StreamableHttpServerParams(url=url) - tools.extend(await mcp_server_tools(server_params)) - else: - logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") - {{/each}} - return tools -{{else}} # ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp" async def get_streamable_http_mcp_tools() -> List[StreamableHttpMcpToolAdapter]: - """Returns MCP Tools compatible with AutoGen.""" + """ + Returns MCP Tools compatible with AutoGen. + """ # to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"} server_params = StreamableHttpServerParams(url=EXAMPLE_MCP_ENDPOINT) return await mcp_server_tools(server_params) -{{/if}} diff --git a/src/assets/python/googleadk/base/mcp_client/client.py b/src/assets/python/googleadk/base/mcp_client/client.py index 91f86c78..f2c1a39c 100644 --- a/src/assets/python/googleadk/base/mcp_client/client.py +++ b/src/assets/python/googleadk/base/mcp_client/client.py @@ -6,13 +6,27 @@ logger = logging.getLogger(__name__) {{#if hasGateway}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} +import httpx +from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth, create_aws_session +{{/if}} + def get_all_gateway_mcp_toolsets() -> list[MCPToolset]: """Returns MCP Toolsets for all configured gateways.""" toolsets = [] {{#each gatewayProviders}} url = os.environ.get("{{envVarName}}") if url: + {{#if (eq authType "AWS_IAM")}} + session = create_aws_session() + auth = SigV4HTTPXAuth(session.get_credentials(), "bedrock-agentcore", session.region_name) + toolsets.append(MCPToolset(connection_params=StreamableHTTPConnectionParams( + url=url, + httpx_client_factory=lambda **kwargs: httpx.AsyncClient(auth=auth, **kwargs) + ))) + {{else}} toolsets.append(MCPToolset(connection_params=StreamableHTTPConnectionParams(url=url))) + {{/if}} else: logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") {{/each}} diff --git a/src/assets/python/googleadk/base/pyproject.toml b/src/assets/python/googleadk/base/pyproject.toml index 49075500..98fd161e 100644 --- a/src/assets/python/googleadk/base/pyproject.toml +++ b/src/assets/python/googleadk/base/pyproject.toml @@ -14,6 +14,8 @@ dependencies = [ "google-adk >= 1.17.0", "bedrock-agentcore >= 1.0.3", "botocore[crt] >= 1.35.0", + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] diff --git a/src/assets/python/langchain_langgraph/base/mcp_client/client.py b/src/assets/python/langchain_langgraph/base/mcp_client/client.py index 484097ac..adcb478a 100644 --- a/src/assets/python/langchain_langgraph/base/mcp_client/client.py +++ b/src/assets/python/langchain_langgraph/base/mcp_client/client.py @@ -6,36 +6,9 @@ {{#if hasGateway}} {{#if (includes gatewayAuthTypes "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request +from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth, create_aws_session {{/if}} - def get_all_gateway_mcp_client() -> MultiServerMCPClient | None: """Returns an MCP Client connected to all configured gateways.""" servers = {} @@ -43,7 +16,9 @@ def get_all_gateway_mcp_client() -> MultiServerMCPClient | None: url = os.environ.get("{{envVarName}}") if url: {{#if (eq authType "AWS_IAM")}} - servers["{{name}}"] = {"transport": "streamable_http", "url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())} + session = create_aws_session() + auth = SigV4HTTPXAuth(session.get_credentials(), "bedrock-agentcore", session.region_name) + servers["{{name}}"] = {"transport": "streamable_http", "url": url, "auth": auth} {{else}} servers["{{name}}"] = {"transport": "streamable_http", "url": url} {{/if}} diff --git a/src/assets/python/langchain_langgraph/base/pyproject.toml b/src/assets/python/langchain_langgraph/base/pyproject.toml index f3b26fe2..80649351 100644 --- a/src/assets/python/langchain_langgraph/base/pyproject.toml +++ b/src/assets/python/langchain_langgraph/base/pyproject.toml @@ -30,6 +30,8 @@ dependencies = [ {{#if (eq modelProvider "Gemini")}} "langchain-google-genai >= 3.0.3", {{/if}} + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] diff --git a/src/assets/python/openaiagents/base/mcp_client/client.py b/src/assets/python/openaiagents/base/mcp_client/client.py index 23ba7eeb..39612c38 100644 --- a/src/assets/python/openaiagents/base/mcp_client/client.py +++ b/src/assets/python/openaiagents/base/mcp_client/client.py @@ -6,36 +6,10 @@ {{#if hasGateway}} {{#if (includes gatewayAuthTypes "AWS_IAM")}} -import boto3 import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request +from mcp_proxy_for_aws.sigv4_helper import SigV4HTTPXAuth, create_aws_session {{/if}} - def get_all_gateway_mcp_servers() -> list[MCPServerStreamableHttp]: """Returns MCP servers for all configured gateways.""" servers = [] @@ -43,7 +17,12 @@ def get_all_gateway_mcp_servers() -> list[MCPServerStreamableHttp]: url = os.environ.get("{{envVarName}}") if url: {{#if (eq authType "AWS_IAM")}} - servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url, "http_client": httpx.AsyncClient(auth=SigV4HTTPXAuth())})) + session = create_aws_session() + auth = SigV4HTTPXAuth(session.get_credentials(), "bedrock-agentcore", session.region_name) + servers.append(MCPServerStreamableHttp( + name="{{name}}", + params={"url": url, "httpx_client_factory": lambda **kwargs: httpx.AsyncClient(auth=auth, **kwargs)} + )) {{else}} servers.append(MCPServerStreamableHttp(name="{{name}}", params={"url": url})) {{/if}} diff --git a/src/assets/python/openaiagents/base/pyproject.toml b/src/assets/python/openaiagents/base/pyproject.toml index 1f535123..61944b9a 100644 --- a/src/assets/python/openaiagents/base/pyproject.toml +++ b/src/assets/python/openaiagents/base/pyproject.toml @@ -13,6 +13,8 @@ dependencies = [ "openai-agents >= 0.4.2", "bedrock-agentcore >= 1.0.3", "botocore[crt] >= 1.35.0", + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] diff --git a/src/assets/python/strands/base/mcp_client/client.py b/src/assets/python/strands/base/mcp_client/client.py index 157029cb..2092903c 100644 --- a/src/assets/python/strands/base/mcp_client/client.py +++ b/src/assets/python/strands/base/mcp_client/client.py @@ -7,33 +7,7 @@ {{#if hasGateway}} {{#if (includes gatewayAuthTypes "AWS_IAM")}} -import boto3 -import httpx -from botocore.auth import SigV4Auth -from botocore.awsrequest import AWSRequest - - -class SigV4HTTPXAuth(httpx.Auth): - """Signs HTTP requests with AWS SigV4 for Lambda function URL authentication.""" - - def __init__(self): - session = boto3.Session() - credentials = session.get_credentials().get_frozen_credentials() - region = session.region_name or os.environ.get("AWS_REGION", "us-east-1") - self.signer = SigV4Auth(credentials, "lambda", region) - - def auth_flow(self, request): - headers = dict(request.headers) - headers.pop("connection", None) - aws_request = AWSRequest( - method=request.method, - url=str(request.url), - data=request.content, - headers=headers, - ) - self.signer.add_auth(aws_request) - request.headers.update(dict(aws_request.headers)) - yield request +from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client {{/if}} {{#each gatewayProviders}} @@ -44,8 +18,7 @@ def get_{{snakeCase name}}_mcp_client() -> MCPClient | None: logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") return None {{#if (eq authType "AWS_IAM")}} - http_client = httpx.AsyncClient(auth=SigV4HTTPXAuth()) - return MCPClient(lambda: streamablehttp_client(url, http_client=http_client)) + return MCPClient(lambda: aws_iam_streamablehttp_client(url, aws_service="bedrock-agentcore")) {{else}} return MCPClient(lambda: streamablehttp_client(url)) {{/if}} diff --git a/src/assets/python/strands/base/pyproject.toml b/src/assets/python/strands/base/pyproject.toml index 3e0b4e6c..d2174547 100644 --- a/src/assets/python/strands/base/pyproject.toml +++ b/src/assets/python/strands/base/pyproject.toml @@ -17,6 +17,8 @@ dependencies = [ {{/if}}"mcp >= 1.19.0", {{#if (eq modelProvider "OpenAI")}}"openai >= 1.0.0", {{/if}}"strands-agents >= 1.13.0", + {{#if hasGateway}}{{#if (includes gatewayAuthTypes "AWS_IAM")}}"mcp-proxy-for-aws >= 1.1.0", + {{/if}}{{/if}} ] [tool.hatch.build.targets.wheel] From 6a1696c8802b862349e4b5d958237fdffe3f054d Mon Sep 17 00:00:00 2001 From: Aidan Daly Date: Wed, 25 Feb 2026 04:15:37 -0500 Subject: [PATCH 6/6] fix: pass AWS region to aws_iam_streamablehttp_client in Strands template --- src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap | 2 +- src/assets/python/strands/base/mcp_client/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index d77491b0..e236c438 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -2744,7 +2744,7 @@ def get_{{snakeCase name}}_mcp_client() -> MCPClient | None: logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") return None {{#if (eq authType "AWS_IAM")}} - return MCPClient(lambda: aws_iam_streamablehttp_client(url, aws_service="bedrock-agentcore")) + return MCPClient(lambda: aws_iam_streamablehttp_client(url, aws_service="bedrock-agentcore", aws_region=os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION")))) {{else}} return MCPClient(lambda: streamablehttp_client(url)) {{/if}} diff --git a/src/assets/python/strands/base/mcp_client/client.py b/src/assets/python/strands/base/mcp_client/client.py index 2092903c..3b77cdac 100644 --- a/src/assets/python/strands/base/mcp_client/client.py +++ b/src/assets/python/strands/base/mcp_client/client.py @@ -18,7 +18,7 @@ def get_{{snakeCase name}}_mcp_client() -> MCPClient | None: logger.warning("{{envVarName}} not set — {{name}} gateway tools unavailable") return None {{#if (eq authType "AWS_IAM")}} - return MCPClient(lambda: aws_iam_streamablehttp_client(url, aws_service="bedrock-agentcore")) + return MCPClient(lambda: aws_iam_streamablehttp_client(url, aws_service="bedrock-agentcore", aws_region=os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION")))) {{else}} return MCPClient(lambda: streamablehttp_client(url)) {{/if}}