Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 123 additions & 0 deletions agentcore-gateway-eventbridge-cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
{
"title": "Amazon Bedrock AgentCore Runtime to Amazon EventBridge via AgentCore Gateway",
"description": "An AI agent on AgentCore Runtime emits business events to EventBridge through a governed AgentCore Gateway MCP tool, authenticated with IAM SigV4.",
"language": "TypeScript",
"level": "300",
"framework": "CDK",
"patternArch": {
"icon1": {
"x": 10,
"y": 50,
"service": "bedrock",
"label": "AgentCore Runtime"
},
"icon2": {
"x": 35,
"y": 20,
"service": "bedrock",
"label": "Amazon Bedrock"
},
"icon3": {
"x": 40,
"y": 50,
"service": "bedrock",
"label": "AgentCore Gateway"
},
"icon4": {
"x": 70,
"y": 50,
"service": "lambda",
"label": "emit_event Lambda"
},
"icon5": {
"x": 95,
"y": 50,
"service": "eventbridge",
"label": "Amazon EventBridge"
},
"line1": {
"from": "icon1",
"to": "icon2"
},
"line2": {
"from": "icon1",
"to": "icon3"
},
"line3": {
"from": "icon3",
"to": "icon4"
},
"line4": {
"from": "icon4",
"to": "icon5"
}
},
"introBox": {
"headline": "How it works",
"text": [
"This pattern shows an AI agent emitting structured business events to Amazon EventBridge through a governed Amazon Bedrock AgentCore Gateway MCP tool.",
"The agent runs on AgentCore Runtime and connects to the Gateway using the MCP Streamable HTTP transport (2025-03-26 spec). The Gateway's inbound authorization is AWS_IAM, so every request must carry a valid AWS SigV4 signature for the bedrock-agentcore service.",
"No MCP client SDK signs streamable-HTTP requests with SigV4 natively, so this pattern signs requests manually: a small helper wraps botocore's SigV4Auth as an httpx.Auth implementation, passed directly to the MCP client's transport.",
"The Gateway exposes an emit_event tool backed by an AWS Lambda function. When the agent calls emit_event with a source, detail type, and payload, the Lambda validates the source prefix (only agent.* allowed) and publishes to EventBridge via PutEvents.",
"IAM permissions follow least privilege: the Runtime execution role has bedrock-agentcore:InvokeGateway scoped to the specific Gateway ARN, the Gateway role can only invoke the specific Lambda, and the Lambda can only PutEvents to the specific bus."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/agentcore-gateway-eventbridge-cdk",
"templateURL": "serverless-patterns/agentcore-gateway-eventbridge-cdk",
"projectFolder": "agentcore-gateway-eventbridge-cdk",
"templateFile": "cdk/lib/agentcore-gateway-eventbridge-stack.ts"
}
},
"resources": {
"bullets": [
{
"text": "Amazon Bedrock AgentCore Gateway",
"link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html"
},
{
"text": "Set up inbound authorization for your gateway (IAM/SigV4)",
"link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-inbound-auth.html"
},
{
"text": "Amazon Bedrock AgentCore Runtime",
"link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html"
},
{
"text": "MCP Streamable HTTP Transport",
"link": "https://modelcontextprotocol.io/specification/2025-03-26/basic/transports"
},
{
"text": "Amazon EventBridge PutEvents",
"link": "https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html"
}
]
},
"deploy": {
"text": [
"cd cdk",
"npm install",
"cdk deploy"
]
},
"testing": {
"headline": "Testing",
"text": [
"See the GitHub repo README.md for detailed testing instructions.",
"Sample invocation payload: {\"prompt\":\"Use the emit_event tool to emit an event with source=agent.claims-processor, detail_type=ClaimApproved, detail={claimId: CLM-001, decision: approved, confidence: 0.94}\"}",
"After invoking, check the Runtime's CloudWatch Logs for a 200 OK from the Gateway MCP endpoint and the EventBridge event ID returned by the Lambda."
]
},
"cleanup": {
"headline": "Cleanup",
"text": ["cd cdk", "cdk destroy"]
},
"authors": [
{
"name": "Antoine Boucherie",
"bio": "Principal Solutions Architect, AWS Global Financial Services",
"linkedin": "antoineboucherie"
}
]
}
103 changes: 103 additions & 0 deletions agentcore-gateway-eventbridge-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Amazon Bedrock AgentCore Runtime to Amazon EventBridge via AgentCore Gateway

This pattern demonstrates how an AI agent on **AgentCore Runtime** emits structured business events to **EventBridge** through a governed **AgentCore Gateway MCP tool**, authenticated with **IAM (SigV4)**. The Gateway provides governance, observability, and schema control over what the agent can emit — without the agent needing direct access to the EventBridge SDK.

The CDK stack is **fully self-contained**: it builds and deploys the agent container, the Gateway with its Lambda tool backend, and an EventBridge custom bus.

![Architecture](architecture.png)

```
Strands Agent (AgentCore Runtime)
│ MCP Streamable HTTP, SigV4-signed
AgentCore Gateway (authorizerType=AWS_IAM)
│ emit_event tool
Lambda tool backend (validates + PutEvents)
EventBridge Custom Bus
```

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Why route through AgentCore Gateway instead of calling EventBridge directly?

In a mesh of many agents, Gateway is a governed chokepoint between agent reasoning and infrastructure side effects:

- **Single point of schema enforcement** — one tool definition constrains what every agent in the mesh can emit.
- **Centralized rate limiting across the fleet** — throughput budgets are enforced at the Gateway, not per agent.
- **Blast radius containment** — only the Gateway's backend touches EventBridge; a misbehaving agent can't take down the bus.
- **Credential isolation** — agents authenticate with scoped, revocable identities
- **Tool discovery in the mesh** — agents find the `emit_event` capability over MCP without any hardcoded SDK dependency.
- **Observability without per-agent instrumentation** — every tool invocation is logged centrally, out of the box.
- **Policy evolution without redeployment** — schema tightening, freezes, or scope changes ship at the Gateway, not in agent code.

The tradeoff is added network latency per emission, which is generally negligible for asynchronous event-driven workflows.

## How it works

1. The agent (Strands, Claude Haiku 4.5) connects to the AgentCore Gateway via the **MCP Streamable HTTP transport** (2025-03-26 spec).
2. The Gateway's inbound authorization is **`AWS_IAM`** — every request must carry a valid AWS SigV4 signature (service `bedrock-agentcore`). The Runtime's execution role is granted `bedrock-agentcore:InvokeGateway` scoped to the Gateway ARN.
3. **No MCP client SDK signs streamable-HTTP requests with SigV4 natively.** This pattern signs requests manually: [`agent-code/sigv4.py`](agent-code/sigv4.py) wraps `botocore.auth.SigV4Auth` as an `httpx.Auth` implementation and passes it to `streamablehttp_client(url, auth=sigv4_auth)`.
4. The Gateway exposes an `emit_event` tool backed by a Lambda function.
5. When the agent decides to emit an event, it calls `emit_event` with `source`, `detail_type`, and `detail`.
6. The Lambda validates the source prefix (`agent.*` only) and calls `events:PutEvents` on the custom bus.

## Prerequisites

- [AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) with sufficient permissions
- [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cli.html) installed and configured
- [Node.js 20+](https://nodejs.org/en/download/) and npm
- [AWS CDK CLI](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html) (`npm i -g aws-cdk`), bootstrapped in the target account/region
- [Docker](https://docs.docker.com/get-docker/) installed and running
- Access to the Amazon Bedrock Claude Haiku 4.5 model (enable in the Amazon Bedrock console)

## Deployment

```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/agentcore-gateway-eventbridge-cdk/cdk
npm install
cdk deploy
```

Note the stack outputs — in particular `AgentRuntimeArn`.

## Testing

Invoke the agent with a prompt that triggers the `emit_event` tool:

```bash
RUNTIME_ARN="<AgentRuntimeArn from stack outputs>"

aws bedrock-agentcore invoke-agent-runtime \
--agent-runtime-arn "$RUNTIME_ARN" \
--qualifier DEFAULT \
--runtime-session-id "test-session-$(uuidgen | tr -d '-')" \
--payload '{"prompt": "Use the emit_event tool to emit an event with source=agent.claims-processor, detail_type=ClaimApproved, detail={claimId: CLM-001, decision: approved, confidence: 0.94}"}' \
--region us-east-1
```

Verify success in the Runtime's CloudWatch Logs (`/aws/bedrock-agentcore/runtimes/<AgentRuntimeId>-DEFAULT`):

- `POST https://<gateway-url>/mcp "HTTP/1.1 200 OK"` confirms the SigV4-signed request to the Gateway succeeded.
- The agent's response includes the EventBridge `Event ID` and a `Failed Count: 0`.

Common failure causes:
- `403 Forbidden` from the Gateway → the Runtime role is missing `bedrock-agentcore:InvokeGateway` on the Gateway ARN, or the SigV4 signature is malformed (check that the `connection` header was stripped before signing).
- `AttributeError` on tool listing → ensure `strands-agents` and `mcp` package versions are compatible (see `agent-code/requirements.txt`).

## Cleanup

```bash
cdk destroy
```

---

Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
5 changes: 5 additions & 0 deletions agentcore-gateway-eventbridge-cdk/agent-code/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
__pycache__
*.pyc
.git
.gitignore
.venv
3 changes: 3 additions & 0 deletions agentcore-gateway-eventbridge-cdk/agent-code/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__pycache__
*.pyc
.venv
20 changes: 20 additions & 0 deletions agentcore-gateway-eventbridge-cdk/agent-code/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
FROM public.ecr.aws/docker/library/python:3.12-slim

WORKDIR /app

RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore

EXPOSE 8080

COPY . .

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/ping || exit 1

CMD ["python", "agent.py"]
67 changes: 67 additions & 0 deletions agentcore-gateway-eventbridge-cdk/agent-code/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
Strands Agent on AgentCore Runtime that connects to an AgentCore Gateway
to discover and use the emit_event MCP tool.

The agent connects to the Gateway using the MCP Streamable HTTP transport.
Authentication: the Gateway's authorizerType is AWS_IAM, so every MCP
request must be signed with SigV4 (service "bedrock-agentcore"). The
Runtime's execution role is granted bedrock-agentcore:InvokeGateway
scoped to this Gateway's ARN. See sigv4.py for the signing implementation
— no MCP client SDK signs streamable-HTTP requests natively, so this is
done manually by wrapping botocore's SigV4Auth as an httpx.Auth.
"""
import os
import logging

from bedrock_agentcore import BedrockAgentCoreApp
from strands import Agent
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamablehttp_client

from sigv4 import SigV4HTTPXAuth

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = BedrockAgentCoreApp()

GATEWAY_URL = os.environ.get("GATEWAY_MCP_URL", "")
AWS_REGION = os.environ.get("AWS_REGION", "us-east-1")


def create_mcp_client():
"""Create a fresh, SigV4-authenticated MCP client per invocation."""
if not GATEWAY_URL:
return None
sigv4_auth = SigV4HTTPXAuth(region=AWS_REGION)
return MCPClient(lambda: streamablehttp_client(GATEWAY_URL, auth=sigv4_auth))


@app.entrypoint
def invoke(payload: dict) -> dict:
"""Process a request and let the agent decide whether to emit events."""
prompt = payload.get("prompt", "No prompt provided.")
logger.info("Received prompt: %s", prompt[:200])

try:
mcp_client = create_mcp_client()
if mcp_client:
with mcp_client:
agent = Agent(
model="us.anthropic.claude-haiku-4-5-20251001-v1:0",
tools=mcp_client.list_tools_sync(),
)
result = agent(prompt)
else:
agent = Agent(model="us.anthropic.claude-haiku-4-5-20251001-v1:0")
result = agent(prompt)

logger.info("Agent completed: %s", str(result)[:500])
return {"status": "completed", "result": str(result)[:2000]}
except Exception as e:
logger.exception("Agent invocation failed")
return {"status": "error", "error": str(e)[:500]}


if __name__ == "__main__":
app.run()
2 changes: 2 additions & 0 deletions agentcore-gateway-eventbridge-cdk/agent-code/requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
strands-agents==1.50.2
bedrock-agentcore==1.18.1
42 changes: 42 additions & 0 deletions agentcore-gateway-eventbridge-cdk/agent-code/sigv4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
SigV4 authentication for the MCP streamable-HTTP transport.

The MCP Python SDK's streamablehttp_client is a plain httpx-based client
with no native AWS SigV4 support. AgentCore Gateway's AWS_IAM inbound
authorizer requires each HTTP request to be signed with SigV4 (service
"bedrock-agentcore"). This wraps botocore's SigV4Auth as an httpx.Auth
so it can be passed directly to streamablehttp_client's `auth=` parameter.

Reference pattern: awslabs/agentcore-samples gatewaylabproject/streamable_http_sigv4.py
"""
import boto3
import httpx
from botocore.auth import SigV4Auth
from botocore.awsrequest import AWSRequest


class SigV4HTTPXAuth(httpx.Auth):
"""httpx.Auth implementation that signs requests with AWS SigV4."""

def __init__(self, region: str, service: str = "bedrock-agentcore"):
session = boto3.Session()
credentials = session.get_credentials()
if credentials is None:
raise RuntimeError("No AWS credentials available to sign Gateway requests")
self._signer = SigV4Auth(credentials, service, region)

def auth_flow(self, request: httpx.Request):
headers = dict(request.headers)
# The "connection" header is not part of the canonical request and
# including it breaks the signature validation on the server side.
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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions agentcore-gateway-eventbridge-cdk/cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
node_modules
cdk.out
*.js
!jest.config.js
*.d.ts
.cdk.staging
*.tsbuildinfo
14 changes: 14 additions & 0 deletions agentcore-gateway-eventbridge-cdk/cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { AgentCoreGatewayEventBridgeStack } from '../lib/agentcore-gateway-eventbridge-stack';

const app = new cdk.App();

new AgentCoreGatewayEventBridgeStack(app, 'AgentCoreGatewayEventBridgeStack', {
description:
'ServerlessLand pattern: AgentCore Runtime agent emits events to EventBridge via AgentCore Gateway MCP tool',
env: {
account: process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEFAULT_REGION,
},
});
Loading