Akshay Parkhi's Weblog

Subscribe

nite Multiple MCP Servers Through Amazon Bedrock AgentCore Gateway

31st March 2026

As AI agents scale in enterprises, teams build dozens of specialized MCP (Model Context Protocol) servers — one for order management, another for product catalog, yet another for promotions. Each server has its own endpoint, its own auth, its own tool definitions. The agent that consumes these tools suddenly becomes an integration nightmare.

Amazon Bedrock AgentCore Gateway solves this by acting as a single front door to all your MCP servers. In this post, we’ll deploy two MCP servers with separate authentication providers behind one gateway, prove the unified auth model works, and dig into the internals of how the gateway handles tool caching, routing, and session management.

Architecture Overview

                          ┌─── Order MCP Server (Cognito Pool A)
Agent ──(1 token)──> AgentCore Gateway ──┤
                          └─── Catalog MCP Server (Cognito Pool B)

The agent authenticates once with the gateway. The gateway handles outbound auth to each MCP server independently. The agent never sees backend credentials.

What We’ll Build

  • Order MCP Server — tools for getOrder, updateOrder, cancelOrder
  • Catalog MCP Server — tools for searchProducts, getProductDetails, checkInventory
  • AgentCore Gateway — single entry point with JWT auth
  • Strands Agent — AI agent that discovers and invokes all 6 tools through the gateway

Each MCP server has its own Cognito user pool (simulating different teams with different auth providers). The agent only knows about the gateway’s Cognito pool.

Step 1: Create the MCP Servers

Order Management Server

from mcp.server.fastmcp import FastMCP

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def getOrder(orderId: int) -> dict:
    """Get details of an existing order by order ID"""
    return {
        "orderId": orderId,
        "status": "shipped",
        "items": [{"name": "Widget A", "qty": 2, "price": 29.99}],
        "total": 59.98,
    }

@mcp.tool()
def updateOrder(orderId: int, status: str = "processing") -> dict:
    """Update an existing order's status"""
    return {"orderId": orderId, "previousStatus": "pending", "newStatus": status, "updated": True}

@mcp.tool()
def cancelOrder(orderId: int) -> dict:
    """Cancel an existing order by order ID"""
    return {"orderId": orderId, "status": "cancelled", "refundInitiated": True}

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

Product Catalog Server

from mcp.server.fastmcp import FastMCP

mcp = FastMCP(host="0.0.0.0", stateless_http=True)

@mcp.tool()
def searchProducts(query: str) -> dict:
    """Search the product catalog by keyword"""
    return {
        "query": query,
        "results": [
            {"id": 101, "name": "Widget A", "price": 29.99, "inStock": True},
            {"id": 102, "name": "Widget B", "price": 49.99, "inStock": True},
            {"id": 103, "name": "Gadget Pro", "price": 99.99, "inStock": False},
        ],
    }

@mcp.tool()
def getProductDetails(productId: int) -> dict:
    """Get detailed information about a specific product"""
    return {"id": productId, "name": "Widget A", "price": 29.99, "inStock": True, "rating": 4.5}

@mcp.tool()
def checkInventory(productId: int) -> dict:
    """Check real-time inventory levels for a product"""
    return {"productId": productId, "available": 142, "warehouse": "US-East"}

if __name__ == "__main__":
    mcp.run(transport="streamable-http")

Two requirements for AgentCore Runtime compatibility: stateless_http=True and host="0.0.0.0" on default port 8000.

Step 2: Set Up Authentication

We create three separate Cognito user pools to demonstrate the unified auth model:

PoolPurposeWho uses it
Gateway PoolInbound auth — who can call the gatewayAgent
Order Runtime PoolOutbound auth — gateway calls Order serverGateway
Catalog Runtime PoolOutbound auth — gateway calls Catalog serverGateway
# Create Gateway Cognito Pool (agent authenticates here)
gateway_pool = cognito_client.create_user_pool(PoolName="AgentCoreGatewayPool")
cognito_client.create_resource_server(
    UserPoolId=gateway_pool_id,
    Identifier="agentcore-gateway",
    Scopes=[{"ScopeName": "invoke", "ScopeDescription": "Invoke gateway tools"}],
)
gateway_app = cognito_client.create_user_pool_client(
    UserPoolId=gateway_pool_id,
    AllowedOAuthFlows=["client_credentials"],
    AllowedOAuthScopes=["agentcore-gateway/invoke"],
    GenerateSecret=True,
)

Step 3: Create the AgentCore Gateway

gateway_client = boto3.client("bedrock-agentcore-control")

auth_config = {
    "customJWTAuthorizer": {
        "allowedClients": [gateway_client_id],
        "discoveryUrl": gateway_discovery_url,
    }
}

create_response = gateway_client.create_gateway(
    name="DemoGateway",
    roleArn=role_arn,
    protocolType="MCP",
    authorizerType="CUSTOM_JWT",
    authorizerConfiguration=auth_config,
)
gateway_id = create_response["gatewayId"]
gateway_url = create_response["gatewayUrl"]

Step 4: Deploy MCP Servers to AgentCore Runtime

from bedrock_agentcore_starter_toolkit import Runtime

agentcore_runtime = Runtime()
agentcore_runtime.configure(
    entrypoint="server.py",
    auto_create_execution_role=True,
    auto_create_ecr=True,
    requirements_file="requirements.txt",
    region=region,
    authorizer_configuration=runtime_auth_config,
    protocol="MCP",
    agent_name="mcp_server_agentcore",
)
launch_result = agentcore_runtime.launch()

The toolkit handles Dockerfile generation, ECR repository creation, CodeBuild, and Runtime agent registration. Repeat for the catalog server with its own Cognito pool.

Step 5: Add MCP Servers as Gateway Targets

# Create credential provider for outbound auth
cognito_provider = identity_client.create_oauth2_credential_provider(
    name="gateway-mcp-server-identity",
    credentialProviderVendor="CustomOauth2",
    oauth2ProviderConfigInput={
        "customOauth2ProviderConfig": {
            "oauthDiscovery": {"discoveryUrl": runtime_discovery_url},
            "clientId": runtime_client_id,
            "clientSecret": runtime_client_secret,
        }
    },
)

# Add MCP server as gateway target
gateway_client.create_gateway_target(
    name="mcp-server-target",
    gatewayIdentifier=gateway_id,
    targetConfiguration={"mcp": {"mcpServer": {"endpoint": mcp_url}}},
    credentialProviderConfigurations=[{
        "credentialProviderType": "OAUTH",
        "credentialProvider": {
            "oauthCredentialProvider": {
                "providerArn": cognito_provider_arn,
                "scopes": ["agentcore-runtime/invoke"],
            }
        },
    }],
)

When create_gateway_target is called, the gateway performs an implicit synchronisation — it connects to the MCP server, calls tools/list, caches the tool definitions, and generates embeddings for semantic search.

Step 6: Test with the Agent

from strands import Agent
from strands.models.bedrock import BedrockModel
from mcp.client.streamable_http import streamablehttp_client
from strands.tools.mcp.mcp_client import MCPClient

# ONE token for the gateway — agent never sees backend credentials
token = get_cognito_token(gateway_pool_id, gateway_client_id, gateway_client_secret)

# ONE connection to the gateway
def create_transport():
    return streamablehttp_client(gateway_url, headers={"Authorization": f"Bearer {token}"})

client = MCPClient(create_transport)
with client:
    tools = client.list_tools_sync()  # Returns ALL tools from ALL servers
    agent = Agent(model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-6"), tools=tools)
    agent("Search for widgets in the catalog, then check order 42")

Test Results

Tool discovery — 6 tools from 2 servers, 1 connection:

Order Server tools (3):
  - mcp-server-target___cancelOrder
  - mcp-server-target___getOrder
  - mcp-server-target___updateOrder

Catalog Server tools (3):
  - catalog-server-target___searchProducts
  - catalog-server-target___getProductDetails
  - catalog-server-target___checkInventory

Cross-server invocation — single prompt hits both backends:

Prompt: "Search for widgets in the catalog, then check order 42"

Tool #1: catalog-server-target___searchProducts → 3 products found
Tool #2: mcp-server-target___getOrder → Order 42 contains Widget A (shipped)

"Order #42 already contains 2x Widget A and has been shipped."

Auth summary:

Tokens obtained by agent:           1 (gateway token)
Tokens managed by gateway:          2 (one per backend server)
MCP connections by agent:           1 (to gateway)
Backend credentials seen by agent:  0

How the Gateway Works Internally

Tool caching and synchronisation

When you add a gateway target, the gateway pulls tool definitions from the MCP server:

What’s pulledExample
Tool namegetOrder → stored as mcp-server-target___getOrder
Description"Get details of an existing order by order ID"
Input schema{"orderId": {"type": "integer"}} (from Python type hints)
EmbeddingVector representation for semantic search

tools/list reads from this cache — it never hits the live MCP server. tools/call is real-time — the gateway forwards to the live MCP server with a fresh OAuth token.

To refresh the cache after deploying new tools:

gateway_client.synchronize_gateway_targets(
    gatewayIdentifier=gateway_id,
    targetId=target_id,
)

Naming collision prevention

The gateway automatically prefixes tool names with the target name using triple underscores:

Target "mcp-server-target":     getOrder → mcp-server-target___getOrder
Target "catalog-server-target": getOrder → catalog-server-target___getOrder

Teams name their tools freely. The gateway namespaces them during sync.

Session management and microVM routing

Request 1 (no session ID) → new microVM spins up (cold start) → returns session ID "abc"
Request 2 (session ID "abc") → same microVM (warm, fast)
Request 3 (session ID "abc") → same microVM (warm, fast)

Stateless mode (stateless_http=True): session ID is an optimisation. Losing it means a cold start, but the request still works — any microVM can handle any request.

Stateful mode (stateless_http=False): session ID is required. The server holds state in memory. Losing the session ID breaks the workflow because the state lives on a specific microVM.

Hidden Values: What the Gateway Gives You

Protocol translation — your REST APIs become MCP tools

targetConfiguration = {
    "mcp": {
        "mcpServer":     {"endpoint": "https://..."},          # MCP server
        "lambda":        {"lambdaArn": "arn:aws:lambda:..."},  # Lambda function
        "openApiSchema": {"s3": {"uri": "s3://..."}},          # REST API via OpenAPI
        "apiGateway":    {"restApiId": "...", "stage": "..."},  # API Gateway REST API
    }
}

Your existing REST APIs become MCP tools without writing an MCP server. The agent calls tools/call and the gateway converts it to an HTTP request, Lambda invocation, or AWS service call.

API Gateway integration with tool filtering

"apiGatewayToolConfiguration": {
    "toolFilters": [
        {"filterPath": "/orders/*", "methods": ["GET", "POST"]},
        # /admin/* endpoints are NOT exposed
    ],
    "toolOverrides": [
        {
            "name": "getOrder",
            "description": "Fetch order by ID",   # override auto-generated description
            "path": "/orders/{id}",
            "method": "GET",
        }
    ]
}

Credential rotation without agent downtime

# Backend team rotates credentials — zero agent changes required
identity_client.update_oauth2_credential_provider(
    name="gateway-mcp-server-identity",
    oauth2ProviderConfigInput={
        "customOauth2ProviderConfig": {
            "clientId": same_client_id,
            "clientSecret": "NEW_ROTATED_SECRET",
        }
    },
)

Three auth methods per target

TypeUse case
OAUTHMCP servers with Cognito/OAuth2
API_KEYThird-party MCP servers with API key auth
GATEWAY_IAM_ROLEAWS services that use SigV4/IAM

One gateway can route to an MCP server via OAuth, a third-party API via API key, and a Lambda via IAM — all from the same agent connection.

Failure isolation between targets

Agent: "Search products and check order 42"

catalog-server-target___searchProducts → Catalog server (UP) → ✅ results
mcp-server-target___getOrder           → Order server (DOWN)  → ❌ this tool only

The catalog call succeeds even when the order server is down. Without a gateway, a shared connection failure takes out all tools.

Gateway federation

Regional Gateway (US)   ──┐
Regional Gateway (EU)   ──┼──> Global Gateway ──> Agent
Regional Gateway (APAC) ──┘

One AgentCore Gateway can serve as a target for another gateway. Each region manages its own MCP servers. A global gateway aggregates them. Organizational boundaries become routing boundaries.

When to Use AgentCore Gateway

Use it when:

  • Multiple MCP servers across teams
  • Different auth providers per backend
  • Mixed backends (MCP + Lambda + REST APIs)
  • Need centralized tool management and discovery

Skip it when: single MCP server, single agent — direct connection is simpler and one less network hop.

Project Structure

agentcore_gateway/
├── mcp_server/
│   ├── server.py              # Order Management MCP server
│   └── requirements.txt
├── mcp_server_catalog/
│   ├── server.py              # Product Catalog MCP server
│   └── requirements.txt
├── agent/
│   └── ordering_agent.py      # Connects via gateway
└── scripts/
    ├── 01_setup_cognito.py    # Create 3 Cognito pools
    ├── 02_setup_iam.py        # IAM role for AgentCore
    ├── 03_deploy_gateway.py   # Gateway + Order server + target
    ├── 04_test_agent.py       # Basic agent test
    ├── 05_cleanup.py          # Tear down all resources
    ├── 06_add_catalog_server.py  # Deploy catalog with separate auth
    └── 07_test_unified_auth.py   # Prove unified auth works
python scripts/01_setup_cognito.py
python scripts/02_setup_iam.py
python scripts/03_deploy_gateway.py
python scripts/06_add_catalog_server.py
python scripts/07_test_unified_auth.py
python scripts/05_cleanup.py

AgentCore Gateway turns MCP server sprawl into an infrastructure concern rather than an application concern. Teams own their MCP servers. The platform team manages the gateway. Agents connect to one endpoint with one token. As you add server 3, 4, 5, and beyond — zero agent code changes.

The core insight: AgentCore Gateway is to MCP servers what API Gateway is to REST APIs — centralised routing, auth, discovery, and management. Without it, every agent is its own integration layer.

This is nite Multiple MCP Servers Through Amazon Bedrock AgentCore Gateway by Akshay Parkhi, posted on 31st March 2026.

Previous: OpenUSD: Advanced Patterns and Common Gotchas.