diff --git a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap index 89ef1df5..e236c438 100644 --- a/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap +++ b/src/assets/__tests__/__snapshots__/assets.snapshot.test.ts.snap @@ -1598,7 +1598,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 @@ -1616,7 +1620,12 @@ def add_numbers(a: int, b: int) -> int: # Get MCP Toolset -mcp_toolset = [get_streamable_http_mcp_client()] +{{#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 @@ -1691,21 +1700,51 @@ 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 (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}} + 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" 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}} " `; @@ -1776,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] @@ -1878,7 +1919,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 @@ -1908,10 +1953,16 @@ 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 = 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 +1988,43 @@ 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 (includes gatewayAuthTypes "AWS_IAM")}} +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 = {} + {{#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) + servers["{{name}}"] = {"transport": "streamable_http", "url": url, "auth": auth} + {{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 + 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" 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 +2034,7 @@ def get_streamable_http_mcp_client() -> MultiServerMCPClient: } } ) +{{/if}} " `; @@ -2124,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] @@ -2224,13 +2305,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 @@ -2252,16 +2342,47 @@ 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 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 }}", + 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) return result + {{/if}} except Exception as e: log.error(f"Error during agent execution: {e}", exc_info=True) raise e @@ -2292,20 +2413,50 @@ 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 (includes gatewayAuthTypes "AWS_IAM")}} +import httpx +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 = [] + {{#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) + 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}} + 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" 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}} " `; @@ -2371,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] @@ -2469,7 +2622,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}} @@ -2478,7 +2635,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 = [] @@ -2490,6 +2651,11 @@ def add_numbers(a: int, b: int) -> int: return a+b tools.append(add_numbers) +# Add MCP client to tools if available +for mcp_client in mcp_clients: + if mcp_client: + tools.append(mcp_client) + {{#if hasMemory}} def agent_factory(): @@ -2504,7 +2670,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 +2686,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 +2724,51 @@ 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 (includes gatewayAuthTypes "AWS_IAM")}} +from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client +{{/if}} + +{{#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("{{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", aws_region=os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION")))) + {{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" 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`] = ` @@ -2724,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/googleadk/base/main.py b/src/assets/python/googleadk/base/main.py index 2e89f01a..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,7 +27,12 @@ def add_numbers(a: int, b: int) -> int: # Get MCP Toolset -mcp_toolset = [get_streamable_http_mcp_client()] +{{#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/googleadk/base/mcp_client/client.py b/src/assets/python/googleadk/base/mcp_client/client.py index 777e2836..f2c1a39c 100644 --- a/src/assets/python/googleadk/base/mcp_client/client.py +++ b/src/assets/python/googleadk/base/mcp_client/client.py @@ -1,15 +1,45 @@ +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 (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}} + 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" 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/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/main.py b/src/assets/python/langchain_langgraph/base/main.py index 88bfe2d8..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,10 +38,16 @@ 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 = 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..adcb478a 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,40 @@ +import os +import logging from langchain_mcp_adapters.client import MultiServerMCPClient +logger = logging.getLogger(__name__) + +{{#if hasGateway}} +{{#if (includes gatewayAuthTypes "AWS_IAM")}} +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 = {} + {{#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) + servers["{{name}}"] = {"transport": "streamable_http", "url": url, "auth": auth} + {{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 + 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" 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 +44,4 @@ def get_streamable_http_mcp_client() -> MultiServerMCPClient: } } ) +{{/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/main.py b/src/assets/python/openaiagents/base/main.py index d75f6002..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,16 +39,47 @@ 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 hasGateway}} + if mcp_servers: agent = Agent( name="{{ name }}", model="gpt-4.1", - mcp_servers=active_servers, + 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 }}", + 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=[], + tools=[add_numbers] + ) + 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/openaiagents/base/mcp_client/client.py b/src/assets/python/openaiagents/base/mcp_client/client.py index 9796d575..39612c38 100644 --- a/src/assets/python/openaiagents/base/mcp_client/client.py +++ b/src/assets/python/openaiagents/base/mcp_client/client.py @@ -1,14 +1,44 @@ +import os +import logging from agents.mcp import MCPServerStreamableHttp +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_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")}} + 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}} + 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" 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/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/main.py b/src/assets/python/strands/base/main.py index a5557405..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 = [] @@ -22,6 +30,11 @@ def add_numbers(a: int, b: int) -> int: return a+b tools.append(add_numbers) +# Add MCP client to tools if available +for mcp_client in mcp_clients: + if mcp_client: + tools.append(mcp_client) + {{#if hasMemory}} def agent_factory(): @@ -36,7 +49,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 +65,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..3b77cdac 100644 --- a/src/assets/python/strands/base/mcp_client/client.py +++ b/src/assets/python/strands/base/mcp_client/client.py @@ -1,12 +1,44 @@ +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 (includes gatewayAuthTypes "AWS_IAM")}} +from mcp_proxy_for_aws.client import aws_iam_streamablehttp_client +{{/if}} + +{{#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("{{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", aws_region=os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION")))) + {{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" 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/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] 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..996fab70 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,11 @@ 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, + gatewayAuthTypes: [...new Set(gatewayProviders.map(g => g.authType))], }; } diff --git a/src/cli/templates/render.ts b/src/cli/templates/render.ts index 166c90a3..e56a8490 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(/[^a-zA-Z0-9]/g, '_').toLowerCase(); +}); /** * Renames template files to their actual names. diff --git a/src/cli/templates/types.ts b/src/cli/templates/types.ts index 37dded4e..dbde6651 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,15 @@ 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[]; + /** Unique auth types across all gateways (for conditional imports) */ + gatewayAuthTypes: string[]; } 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);