
Aws Agentic Ai
- 482 installs
- 342 repo stars
- Updated June 15, 2026
- zxkane/aws-skills
aws-agentic-ai is an agent skill that deploys filesystem-based agent frameworks with S3-mounted shared skills and config on AWS AgentCore.
About
aws-agentic-ai is an agent skill from the AWS skills collection that explains how to run filesystem-based agent frameworks—Claude Agent SDK, OpenClaw, and Strands Agents—with Amazon S3 Files as the shared configuration source of truth. Solo builders shipping agent products often duplicate SKILL.md trees per instance or redeploy on every prompt tweak; this skill shows an NFS-mounted bucket pattern so CLAUDE.md, `.claude/skills/*/SKILL.md`, commands, output styles, and OpenClaw directories stay consistent across runtimes. The core insight is discovery-at-startup: frameworks read the working directory, so centralizing those files in S3 turns skill edits into live capability changes. It targets AWS AgentCore and Runtime setups where you need horizontal scale without config drift. Use during Build when wiring agent hosting, and during Operate when you iterate skills in production. Pair with your existing AWS account guardrails; the skill is architectural guidance rather than a turnkey Terraform apply.
- Maps Claude Agent SDK, OpenClaw, and Strands Agents config discovery to a shared S3 Files NFS layout
- Update SKILL.md or CLAUDE.md in S3 and agent instances pick up new capabilities without redeployment
- Documents AgentCore + S3 Files architecture for multi-instance agent fleets
- Tables tie each framework to key config files and discovery mechanisms (cwd, gateway dir, Python modules)
- Applies to Runtime, S3 Files, and filesystem-based agent frameworks on AWS
Aws Agentic Ai by the numbers
- 482 all-time installs (skills.sh)
- Ranked #1,812 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Aug 3, 2026 (Skillselion catalog sync)
npx skills add https://github.com/zxkane/aws-skills --skill aws-agentic-aiAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 482 |
|---|---|
| repo stars | ★ 342 |
| Security audit | 2 / 3 scanners passed |
| Last updated | June 15, 2026 |
| Repository | zxkane/aws-skills ↗ |
What it does
Deploy Claude Agent SDK, OpenClaw, or Strands-style agents with S3-backed shared SKILL.md and config so capability updates do not require redeploys.
Who is it for?
Best when you're running multiple Claude or OpenClaw agents on AWS and want hot-updatable skills and shared project settings at scale.
Skip if: Single-local-agent hobby setups with no AWS footprint, or teams that only need generic Lambda snippets without S3 Files NFS mounting.
When should I use this skill?
Deploying or scaling filesystem-based agent frameworks on AWS where CLAUDE.md, SKILL.md, OpenClaw, or Strands tooling must stay consistent across instances via S3 Files.
What you get
Agents read a single S3-backed filesystem for CLAUDE.md and skill trees, so configuration edits propagate to all instances on the next file access without redeployment.
- Architecture for S3-backed shared agent configuration trees
- Mapped layout of framework config files to bucket paths
- Operational workflow to update skills without redeploying agents
By the numbers
- Compares three agent frameworks (Claude Agent SDK, OpenClaw, Strands Agents) with config discovery tables
Files
AWS Bedrock AgentCore
AWS Bedrock AgentCore provides a complete platform for deploying and scaling AI agents with nine core services. This skill covers service selection, deployment patterns, and integration workflows using AWS CLI.
How to use this skill: Identify the service(s) the user needs from the table below, then read the corresponding service README before responding. For cross-service patterns (credentials, security, registry integration), check the Cross-Service Resources section. Verify AWS-specific details using the MCP documentation tools.
AWS Documentation Requirement
Always verify AWS facts using MCP tools before answering. Two documentation sources are available:
- AgentCore-specific docs (
mcp__acdocs__*) — bundled with this plugin, providessearch_agentcore_docsandfetch_agentcore_docfor AgentCore documentation - General AWS docs (
mcp__aws-mcp__*ormcp__*awsdocs*__*) — loaded via theaws-mcp-setupdependency for broader AWS documentation
Prefer the AgentCore docs MCP for AgentCore-specific questions. If MCP tools are unavailable, guide the user through the aws-mcp-setup skill's setup flow.
Available Services
| Service | Use For | Documentation |
|---|---|---|
| Gateway | Converting REST APIs to MCP tools | `services/gateway/README.md` |
| Runtime | Deploying and scaling agents | `services/runtime/README.md` |
| Memory | Managing conversation state | `services/memory/README.md` |
| Identity | Credential and access management | `services/identity/README.md` |
| Code Interpreter | Secure code execution in sandboxes | `services/code-interpreter/README.md` |
| Browser | Web automation and scraping | `services/browser/README.md` |
| Observability | Tracing and monitoring | `services/observability/README.md` |
| Agent Registry | Catalog, discover, and govern agents/tools (Preview) | `services/registry/README.md` |
| Evaluations | Automated agent quality assessment (LLM-as-a-Judge) | `services/evaluations/README.md` |
Common Workflows
Deploying a Gateway Target
Read `services/gateway/README.md` before implementing — Gateway setup involves deployment strategies, IAM, and auth choices that vary significantly by use case.
1. Upload OpenAPI schema to S3 2. (API Key auth only) Create credential provider and store API key 3. Create gateway target linking schema (and credentials if using API key) 4. Verify target status and test connectivity
Credential provider is only needed for API key authentication. Lambda targets use IAM roles, and MCP servers use OAuth.
Managing Credentials
Read `cross-service/credential-management.md` first — credential patterns differ across services and getting them wrong causes hard-to-debug auth failures.
1. Use Identity service credential providers for all API keys 2. Link providers to gateway targets via ARN references 3. Rotate credentials quarterly through credential provider updates 4. Monitor usage with CloudWatch metrics
Discovering Agents and Tools (Agent Registry)
Read `services/registry/README.md` first — the registry has governance workflows, MCP endpoint options, and sync modes that affect how records become discoverable.
1. Create a registry to catalog your organization's AI resources 2. Register resources (MCP servers, agents, skills, custom) with descriptive metadata 3. Submit records for approval (auto-approve for dev, manual for production) 4. Search and discover approved resources via CLI or MCP endpoint
Agent Registry is in Preview. Available in us-east-1, us-west-2, eu-west-1, ap-northeast-1, ap-southeast-2.
Evaluating Agent Quality
Read `services/evaluations/README.md` first — evaluators, scoring modes, and IAM setup vary between online monitoring and on-demand testing.
1. Instrument the agent with OpenTelemetry (ADOT) for trace collection 2. Create evaluators (use built-in like Builtin.Helpfulness or create custom) 3. Set up online evaluation with sampling rate and data source 4. Monitor scores in CloudWatch dashboards; investigate low-scoring sessions
Monitoring Agents
Read `services/observability/README.md` for the full monitoring setup — observability configuration depends on your Runtime protocol and framework choice.
1. Enable observability for agents 2. Configure CloudWatch dashboards for metrics 3. Set up alarms for error rates and latency 4. Use X-Ray for distributed tracing
Deep-Dive References
Each service README (linked in the table above) contains sub-links to getting-started guides, troubleshooting, and advanced topics. Start with the service README and follow pointers from there.
Advanced Runtime & OAuth References
Deep-dive reference documentation for Runtime internals, deployment, OAuth integration, and communication protocols. Read these when building production Runtime deployments or configuring OAuth authentication:
- OAuth Integration: `references/agentcore-oauth-integration.md` - Three-layer OAuth architecture (Inbound JWT, Outbound Credential Provider, Gateway OAuth), Cognito configuration, supported IdPs, end-to-end CDK examples
- Runtime Core Mechanisms: `references/agentcore-runtime-core.md` - Container contract, MicroVM Session model, Agent lifecycle (per-request vs per-session), tool integration (MCP/HTTP), startup flow
- Runtime Deployment & Operations: `references/agentcore-runtime-deploy.md` - CDK deployment (L1/L2 constructs), multi-Runtime architecture, security model, observability (OTel/CloudWatch), BedrockAgentCoreApp vs FastAPI comparison
- Runtime Protocol Reference: `references/agentcore-runtime-protocols.md` - HTTP, MCP, A2A, AG-UI protocol specifications with container contracts, endpoint specs, and selection guide
Runnable Script Templates
Production-ready templates in `scripts/` for common deployment patterns:
| Script | Protocol | Description |
|---|---|---|
| `Dockerfile.runtime-template` | — | ARM64 multi-stage Docker build for AgentCore Runtime |
| `runtime-fastapi-template.py` | HTTP | FastAPI Runtime with SSE streaming and MCPClient |
| `mcp-server-template.py` | MCP | MCP Server with Streamable HTTP transport |
| `a2a-server-template.py` | A2A | A2A Server with Agent Card discovery |
| `agui-server-template.py` | AG-UI | AG-UI Server with standard AG-UI event stream |
| `gateway-custom-resource-lambda.py` | — | CDK Custom Resource Lambda for Gateway lifecycle |
Cross-Service Resources
For patterns and best practices that span multiple AgentCore services:
- Credential Management: `cross-service/credential-management.md` - Unified credential patterns, security practices, rotation procedures
- Registry Integration: `cross-service/registry-integration.md` - Cross-service patterns with Gateway, Identity, Runtime
- Security & Resource Policies: `cross-service/security-resource-policies.md` - Resource-based policies, cross-account access, VPC/IP restrictions
- Agent Deployment with S3 Files: `cross-service/agent-persistence-patterns.md` - Deploy Strands Agents, OpenClaw, Claude Agent SDK on AgentCore with S3 Files and Session Storage
Additional Resources
- AWS Documentation: Amazon Bedrock AgentCore
- API Reference: Bedrock AgentCore Control Plane API
- AWS CLI Reference: bedrock-agentcore-control commands
Deploying Filesystem-Based Agent Frameworks with S3 Files and AgentCore
Applies to: Runtime, S3 Files, Claude Agent SDK, OpenClaw, Strands Agents
Core Insight
Modern agent frameworks discover capabilities by reading configuration files from the working directory at startup:
| Framework | Key Config Files | Discovery Mechanism |
|---|---|---|
| Claude Agent SDK | CLAUDE.md, .claude/skills/*/SKILL.md, .claude/commands/*.md, .claude/output-styles/*.md | cwd + setting_sources=["project"] |
| OpenClaw | .openclaw/, .agents/, skills/, .codex, .env | Gateway working directory |
| Strands Agents | Agent code, requirements.txt, tool definitions | Python module loading |
Modify a SKILL.md → agent gains new capabilities. Update CLAUDE.md → agent follows new guidelines. No redeployment.
S3 Files mounts an S3 bucket as a shared NFS filesystem. All agent instances see identical configuration files. Update once in S3 → every instance picks it up on the next file access.
Architecture
┌────────────────────────────────────────────────────┐
│ S3 Bucket (agent configuration source of truth) │
│ │
│ ├── CLAUDE.md │
│ ├── .claude/skills/*/SKILL.md │
│ ├── .claude/commands/*.md │
│ ├── .openclaw/skills/ │
│ └── shared-knowledge/ │
└─────────────┬───────────────────────────────────────┘
│ S3 Files NFS (auto bidirectional sync)
▼
┌─────────────────┐ ┌──────────────────┐ ┌──────────────┐
│ EC2 / EKS / ECS │ │ EC2 / EKS / ECS │ │ Lambda │
│ /mnt/s3-config/ │ │ /mnt/s3-config/ │ │ /mnt/s3/ │
│ (Claude Agent) │ │ (OpenClaw) │ │ (Strands) │
└─────────────────┘ └──────────────────┘ └──────────────┘AgentCore Runtime microVMs cannot mount S3 Files directly. For AgentCore deployments, pull shared config from S3 via API at session start, or use Session Storage for per-session file persistence.
Claude Agent SDK Deployment
With S3 Files (EC2/EKS)
Point cwd to the S3 Files mount. The SDK reads CLAUDE.md, skills, commands, and output styles transparently:
from claude_agent_sdk import query, ClaudeAgentOptions
S3_MOUNT = "/mnt/s3-config" # S3 Files mount point
async def handle_request(prompt: str, session_id: str = None):
options = ClaudeAgentOptions(
cwd=S3_MOUNT,
setting_sources=["project"],
allowed_tools=["Skill", "Read", "Write", "Edit", "Bash", "Glob", "Grep"],
)
if session_id:
options.resume = session_id
async for message in query(prompt=prompt, options=options):
if hasattr(message, "result"):
yield message.resultEnable Bedrock model access (no Anthropic API key required):
export CLAUDE_CODE_USE_BEDROCK=1
# AWS credentials from instance roleZero-Downtime Skill Updates
Upload a new skill to S3 — all running instances discover it on next invocation without restart:
aws s3 cp ./api-testing/SKILL.md \
s3://agent-configs/.claude/skills/api-testing/SKILL.mdOn AgentCore Runtime (S3 API Sync)
Pull shared config from S3 to Session Storage at session start:
import boto3, os
from bedrock_agentcore.runtime import BedrockAgentCoreApp
from claude_agent_sdk import query, ClaudeAgentOptions
import asyncio
app = BedrockAgentCoreApp()
s3 = boto3.client("s3")
WORKSPACE = "/mnt/workspace"
def sync_config():
"""Pull shared agent config from S3 to local workspace."""
for prefix in ["CLAUDE.md", ".claude/skills/", ".claude/commands/"]:
if "/" not in prefix:
obj = s3.get_object(Bucket="agent-configs", Key=prefix)
path = f"{WORKSPACE}/{prefix}"
with open(path, "w") as f:
f.write(obj["Body"].read().decode())
else:
resp = s3.list_objects_v2(Bucket="agent-configs", Prefix=prefix)
for item in resp.get("Contents", []):
local = f"{WORKSPACE}/{item['Key']}"
os.makedirs(os.path.dirname(local), exist_ok=True)
obj = s3.get_object(Bucket="agent-configs", Key=item["Key"])
with open(local, "w") as f:
f.write(obj["Body"].read().decode())
@app.entrypoint
def handler(payload):
sync_config()
async def run():
options = ClaudeAgentOptions(
cwd=WORKSPACE, setting_sources=["project"],
allowed_tools=["Skill", "Read", "Write", "Bash"],
)
result = None
async for msg in query(prompt=payload["prompt"], options=options):
if hasattr(msg, "result"):
result = msg.result
return result
return {"response": asyncio.run(run())}OpenClaw Deployment
With S3 Files (EKS)
Mount two S3 file systems — shared config (read-only across pods) and per-user workspaces:
apiVersion: apps/v1
kind: Deployment
spec:
template:
spec:
containers:
- name: openclaw
volumeMounts:
- name: shared-config
mountPath: /mnt/config # Shared skills, prompts, templates
readOnly: true
- name: user-data
mountPath: /mnt/users # Per-user workspaces
volumes:
- name: shared-config
nfs:
server: <fs-config>.s3-fs.<region>.amazonaws.com
path: /
- name: user-data
nfs:
server: <fs-users>.s3-fs.<region>.amazonaws.com
path: /S3 Files provides automatic bidirectional sync — workspace writes propagate to S3 transparently. Per-user isolation is achieved through path-based separation (/mnt/users/<user-id>/).
On AgentCore Runtime
Use Session Storage (filesystemConfigurations) for per-user workspace persistence. The Router maps each user to a stable runtimeSessionId, so the same user always resumes from the same filesystem state.
Strands Agents Deployment
With S3 Files (EC2/EKS) — Shared Knowledge Base
from strands import Agent, tool
from strands.models import BedrockModel
from strands.session.s3_session_manager import S3SessionManager
KNOWLEDGE = "/mnt/s3-knowledge" # S3 Files mount
@tool
def search_docs(query: str, directory: str = "") -> str:
"""Search shared reference documents.
Args:
query: Search term
directory: Subdirectory to scope search (e.g., 'policies')
"""
import subprocess
path = f"{KNOWLEDGE}/{directory}" if directory else KNOWLEDGE
result = subprocess.run(
["grep", "-rl", query, path, "--include=*.md"],
capture_output=True, text=True
)
return result.stdout or "No matching documents found."
agent = Agent(
model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-20250514-v1:0"),
tools=[search_docs],
session_manager=S3SessionManager(
session_id="user-123", bucket="agent-sessions", prefix="strands/"
),
)On AgentCore Runtime
Use S3SessionManager for conversation persistence and Session Storage for file workspace:
aws bedrock-agentcore-control create-agent-runtime \
--agent-runtime-name "strands-agent" \
--filesystem-configurations '[{"sessionStorage": {"mountPath": "/mnt/workspace"}}]' \
...Decision Matrix
| Factor | S3 Files (EC2/EKS/ECS) | AgentCore + S3 API | AgentCore + Session Storage |
|---|---|---|---|
| Shared config sync | Native — update S3 once, all see it | Manual sync code at session start | Not shared (per-session) |
| Multi-instance sharing | Native NFS | Not supported (per-microVM) | Not supported (per-session) |
| Per-user isolation | Path-based (/users/<id>/) | Native (per-microVM) | Native (per-session) |
| File latency | Sub-millisecond (small files) | 10-100ms (S3 API) | Sub-millisecond (local) |
| Ops complexity | Medium (VPC, mount targets) | Low (fully managed) | Low (fully managed) |
| Claude Agent SDK | cwd → mount point | Sync to Session Storage | cwd → Session Storage |
| Cost | S3 + performance storage + access | Runtime + S3 API calls | Included in Runtime |
Recommended Deployments
| Scenario | Target |
|---|---|
| Multi-instance shared skills/config | EC2/EKS + S3 Files |
| Serverless per-user agent | AgentCore Runtime + Session Storage |
| Claude Agent SDK production service | EC2/EKS + S3 Files (shared CLAUDE.md + skills) |
| Large shared knowledge base | EC2/EKS + S3 Files (NFS access) |
S3 Files Setup
# 1. Create file system
aws s3files create-file-system --bucket <bucket-arn> --role-arn <iam-role-arn> --region <region>
# 2. Create mount target (one per AZ)
aws s3files create-mount-target \
--file-system-id <fs-id> \
--subnet-id <subnet> \
--security-groups <sg>
# 3. Mount on compute
sudo mount -t nfs4 <fs-id>.s3-fs.<region>.amazonaws.com:/ /mnt/s3-config
# 4. Upload agent config
aws s3 sync ./agent-config/ s3://<bucket>/
# 5. Verify
ls /mnt/s3-config/CLAUDE.md
ls /mnt/s3-config/.claude/skills/IAM Requirements
Compute role needs:
s3:GetObject,s3:PutObject,s3:ListBucketon the config bucket (for S3 intelligent read routing)s3files:ClientMount,s3files:ClientWriteon the file system (for NFS client access)
Security
- S3 Files encrypts all data at rest (AWS KMS) and in transit (TLS)
- Restrict NFS access to agent compute via VPC security groups
- For multi-tenant deployments, use separate S3 prefixes per tenant
Related
- Runtime Service — Session Storage for AgentCore
- Memory Service — AgentCore Memory for long-term recall
- Credential Management — IAM roles for S3/S3 Files access
- S3 Files Documentation
- Claude Agent SDK — Skills
- Claude Agent SDK — CLAUDE.md
- OpenClaw
- Strands Session Management
Cross-Service Credential Management
Applies to: Gateway, Runtime, Memory, Identity
Overview
Credential management is a cross-cutting concern across all AgentCore services. This guide provides unified patterns for managing API keys, tokens, and authentication credentials across the AgentCore platform.
Authentication Overview
| Service | Direction | Supported Methods | Use Case |
|---|---|---|---|
| Gateway | Inbound | IAM, JWT, No Auth | Who can invoke MCP tools |
| Gateway | Outbound | IAM, OAuth (2LO/3LO), API Key | Accessing external APIs |
| Runtime | Inbound | IAM (SigV4), JWT | Who can invoke agents |
| Runtime | Outbound | OAuth, API Key | Accessing third-party services |
| Memory | - | IAM Role | Data access permissions |
| Identity | - | AWS KMS | Secret encryption |
Inbound Authorization (Who Can Access Your Services)
Gateway Options (docs):
- IAM Identity: Uses AWS IAM credentials for authorization
- JWT: Tokens from identity providers (Cognito, Microsoft Entra ID, etc.)
- No Authorization: Open access - only for production with proper security controls
Runtime Options (docs):
- IAM (SigV4): Default authentication (works automatically)
- JWT Bearer Token: Token-based auth with discovery URL and audience validation
Note: A Runtime can only use one inbound auth type (IAM or JWT), not both simultaneously.
Outbound Authorization (Accessing External Services)
Gateway Options (docs):
| Target Type | IAM (Service Role) | OAuth 2LO | OAuth 3LO | API Key |
|---|---|---|---|---|
| Lambda function | ✅ | ❌ | ❌ | ❌ |
| API Gateway | ✅ | ❌ | ❌ | ✅ |
| OpenAPI schema | ❌ | ✅ | ✅ | ✅ |
| Smithy schema | ✅ | ✅ | ✅ | ❌ |
| MCP server | ❌ | ✅ | ❌ | ❌ |
- OAuth 2LO: Client credentials grant (machine-to-machine)
- OAuth 3LO: Authorization code grant (user-delegated access)
Runtime Options:
- OAuth: Tokens on behalf of users via Identity Service
- API Key: Key-based authentication via Identity Service
Best Practices
✅ DO's
1. Use Identity Service: Always manage credentials through the Identity service
# ✅ Correct - Use Identity API
aws bedrock-agentcore-control create-api-key-credential-provider \
--name MyCredentialProvider \
--api-key "YOUR_API_KEY_VALUE"2. Separate by Environment: Use different providers for different environments
- dev-api-key-provider # Development
- staging-api-key-provider # Staging
- prod-api-key-provider # Production3. Rotate Regularly: Implement quarterly credential rotation
aws bedrock-agentcore-control update-api-key-credential-provider \
--name MyCredentialProvider \
--api-key "NEW_API_KEY"4. Least Privilege: Grant minimal required permissions to each credential
# API key should only have necessary API permissions
# IAM roles should have scoped-down policies5. Monitor Usage: Track credential usage and set up alerts
{
"CloudWatch Alarms": {
"HighErrorRate": "Alert if > 10% failed requests",
"UnusualActivity": "Alert on usage spikes"
}
}❌ DON'Ts
1. Never Hardcode: Don't embed credentials in code or configuration files
# ❌ Bad - Hardcoded API key
const apiKey = "sk-1234567890abcdef"
# ✅ Good - Reference credential provider
const credentialProvider = "MyCredentialProvider"2. Don't Share Across Environments: Avoid using production keys in development
# ❌ Bad - Same key everywhere
dev: third-party-api-key: prod-key
prod: third-party-api-key: prod-key
# ✅ Good - Separate keys
dev: third-party-api-key: dev-key
prod: third-party-api-key: prod-key3. Don't Commit to Git: Exclude credential files from version control
# .gitignore
*.env
*.secret
credential-*.json4. Don't Use Long-Lived Tokens: Implement token refresh for OAuth
# OAuth tokens should auto-refresh
# Don't use tokens with > 30 day expirationMulti-Service Credential Patterns
Pattern 1: Centralized Identity, Distributed Usage
┌─────────────────────────────────────┐
│ Identity Service │
│ - Stores ALL credentials │
│ - Manages rotation │
│ - Provides audit logs │
└──────────┬──────────────────────────┘
│
├────────────┬────────────┬────────────┐
▼ ▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Gateway │ │ Runtime │ │ Memory │ │ Other │
│ Uses │ │ Uses │ │ Uses │ │ Uses │
└──────────┘ └──────────┘ └──────────┘ └──────────┘Benefits:
- Single source of truth for all credentials
- Unified rotation and audit
- Consistent access patterns
Setup:
# 1. Create master credential in Identity
aws bedrock-agentcore-control create-api-key-credential-provider \
--name MasterAPICredentials \
--api-key "YOUR_MASTER_API_KEY"
# 2. Grant access to each service
# - Gateway: can read MasterAPICredentials
# - Runtime: can read MasterAPICredentials
# - Memory: can read MasterAPICredentialsPattern 2: Service-Specific Credentials
┌─────────────────────────────────────┐
│ Identity Service │
│ - Stores credentials per service │
└──────────┬──────────────────────────┘
│
┌──────┴──────┬────────┬─────────┐
▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌──────┐ ┌─────┐
│ Gateway │ │ Runtime │ │Memory││Other│
│ Cred │ │ Cred │ │ Cred ││Cred │
└─────────┘ └─────────┘ └──────┘ └─────┘Benefits:
- Isolation between services
- Independent rotation per service
- Service-specific permissions
Setup:
# Create separate providers
aws bedrock-agentcore-control create-api-key-credential-provider \
--name GatewayAPICredentials \
--api-key "YOUR_GATEWAY_API_KEY"
aws bedrock-agentcore-control create-api-key-credential-provider \
--name RuntimeCredentials \
--api-key "YOUR_RUNTIME_API_KEY"Pattern 3: Tiered (Master + Service)
┌─────────────────────────────────────┐
│ Identity Service │
│ - Master credential │
│ - Per-service credentials │
└──────────┬──────────────────────────┘
│
┌──────┴──────┐
▼ ▼
┌─────────┐ ┌─────────────┐
│ Master │ │ Services │
│ Cred │ │ - Gateway │ │
└────┬────┘ │ - Runtime │
│ │ - Memory │
└──────┤ (each has │
│ own creds) │
└─────────────┘Use Cases:
- Production: Master credential for critical APIs
- Development: Service-specific credentials for testing
- Emergency: Master credential as backup
Security Best Practices
Encryption
# Use KMS for secret encryption
aws secretsmanager create-secret \
--name MySecret \
--kms-key-id arn:aws:kms:us-west-2:123456789012:key/12345678-abcd-ef12-3456-7890abcdef12 \
--secret-string "my-secret-value"Access Control
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"bedrock-agentcore:GetResourceApiKey"
],
"Resource": "*",
"Condition": {
"StringEquals": {
"aws:PrincipalTag/Service": "gateway"
}
}
}
]
}Audit Logging
# Enable CloudTrail for Bedrock AgentCore
aws cloudtrail create-trail \
--name agentcore-audit \
--s3-bucket-name agentcore-audit-logs \
--include-global-service-events trueRotation Strategy
Automated Rotation
# Enable automatic rotation (when supported)
aws secretsmanager rotate-secret \
--secret-id MySecret \
--lambda-arn arn:aws:lambda:us-west-2:123456789012:function:MyRotationFunction
# Rotation schedule (every 30 days)
aws secretsmanager rotate-secret \
--secret-id MySecret \
--rotation-rules AutomaticAfterDays=30Manual Rotation Process
#!/bin/bash
# rotate-credentials.sh
echo "Step 1: Generate new credential"
NEW_KEY=$(generate-new-api-key)
echo "Step 2: Update in Identity service"
aws bedrock-agentcore-control update-api-key-credential-provider \
--name MyCredentialProvider \
--api-key "$NEW_KEY"
echo "Step 3: Verify all services work"
./test-all-services.sh
echo "Step 4: Delete old credential"
# Old credential is automatically deprecatedCommon Patterns
Pattern: Credential Fallback
// Try primary credential, fallback to backup
async function callWithFallback(provider: string) {
try {
return await callAPI(provider);
} catch (error) {
if (error.code === 'InvalidAPICredentials') {
// Fallback to backup provider
return await callAPI(`${provider}-backup`);
}
throw error;
}
}Pattern: Rate Limiting with Credential Pool
// Rotate through multiple credentials to avoid rate limits
const credentialPool = [
'cred-1',
'cred-2',
'cred-3'
];
let currentIndex = 0;
function getNextCredential(): string {
const credential = credentialPool[currentIndex];
currentIndex = (currentIndex + 1) % credentialPool.length;
return credential;
}Troubleshooting Credential Issues
Issue: "Credential not found"
Diagnosis:
# Check if provider exists
aws bedrock-agentcore-control get-api-key-credential-provider \
--name MyCredentialProvider
# Check IAM permissions
aws iam simulate-principal-policy \
--policy-source-arn arn:aws:iam::123456789012:role/MyRole \
--action-names bedrock-agentcore:GetResourceApiKey \
--resource-arns arn:aws:bedrock-agentcore:us-west-2:123456789012:*Solution: Create provider or grant IAM permissions
---
Issue: "Invalid credentials" after rotation
Diagnosis:
# Check secret value format
aws secretsmanager get-secret-value \
--secret-id arn:aws:secretsmanager:us-west-2:123456789012:secret:MySecret
# Should be: {"apiKey": "valid-key"}Solution: Use correct update API
aws bedrock-agentcore-control update-api-key-credential-provider \
--name MyCredentialProvider \
--api-key "VALID_KEY"---
Issue: Cross-service access denied
Diagnosis:
# Check which services can access the credential
aws bedrock-agentcore-control get-api-key-credential-provider \
--name MyCredentialProvider
# Review service IAM policiesSolution: Add cross-service access policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "bedrock-agentcore:GetResourceApiKey",
"Resource": "*",
"Condition": {
"ArnLike": {
"aws:PrincipalArn": [
"arn:aws:iam::*:role/*gateway*",
"arn:aws:iam::*:role/*runtime*"
]
}
}
}
]
}Cost Considerations
Secrets Manager Costs
- Per secret: ~$0.40/month
- Per 10,000 API calls: ~$0.05
- Cross-region replication: Additional costs
Optimization:
- Share credentials across services when possible
- Use regional replication only when necessary
- Cache credential retrieval (respect security requirements)
Identity Service Costs
- Credential provider storage: Included in Secrets Manager
- API calls: Same as Secrets Manager pricing
- Cross-account access: No additional cost
References
- [Identity Service](../services/identity/README.md): Credential provider management
- [Gateway Service](../services/gateway/README.md): Uses credentials for API authentication
- AWS Secrets Manager: Pricing
- AWS Documentation: Managing AWS Secrets Manager secrets
---
Related Guides:
- Observability Service
- AWS AgentCore Identity Documentation
Cross-Service: Registry Integration Patterns
Applies to: Registry, Gateway, Runtime, Identity
Overview
Agent Registry becomes most powerful when combined with other AgentCore services. This guide covers cross-service patterns for discovering, deploying, and operating AI resources through the registry.
Pattern 1: Registry + Gateway (Discover and Deploy MCP Servers)
Discover MCP servers from the registry, then deploy them as Gateway targets for agent consumption.
┌──────────────┐ search ┌──────────────┐ deploy ┌──────────────┐
│ Developer / │ ────────> │ Agent │ ────────> │ Gateway │
│ Agent │ │ Registry │ │ Target │
└──────────────┘ └──────────────┘ └──────────────┘
│ │
│ MCP schema │ serves tools
▼ ▼
┌──────────────┐ ┌──────────────┐
│ External │ │ AI Agents │
│ MCP Server │ │ (consumers) │
└──────────────┘ └──────────────┘Workflow
REGION="us-east-1"
REGISTRY_ID="reg-abc123"
GATEWAY_ID="gw-xyz789"
# 1. Search registry for a useful MCP server
RESULT=$(aws bedrock-agentcore search-registry-records \
--search-query "payment processing" \
--registry-ids "arn:aws:bedrock-agentcore:${REGION}:$(aws sts get-caller-identity --query Account --output text):registry/${REGISTRY_ID}" \
--filter '{"descriptorType": {"$eq": "MCP"}}' \
--region $REGION)
echo "$RESULT"
# Extract the MCP server schema from the search results
# 2. Save the discovered schema to S3
echo "$RESULT" | jq -r '.records[0].descriptors.mcp.server.inlineContent' > /tmp/discovered-schema.json
aws s3 cp /tmp/discovered-schema.json s3://my-schemas-bucket/discovered/payments-mcp.json
# 3. Deploy as a Gateway target
aws bedrock-agentcore-control create-gateway-target \
--gateway-identifier $GATEWAY_ID \
--name "payments-from-registry" \
--endpoint-configuration '{
"openApiSchema": {
"s3": {"uri": "s3://my-schemas-bucket/discovered/payments-mcp.json"}
}
}' \
--region $REGIONBi-directional Sync
Register your Gateway targets back into the registry so other teams can discover them using URL-based sync:
# Sync a Gateway target into the registry
aws bedrock-agentcore-control create-registry-record \
--registry-id $REGISTRY_ID \
--name "platform/payments-gateway" \
--description "Payment tools exposed via Gateway (team: platform)" \
--descriptor-type MCP \
--synchronization-type URL \
--synchronization-configuration "{
\"fromUrl\": {
\"url\": \"https://bedrock-agentcore.${REGION}.amazonaws.com/gateway/${GATEWAY_ID}/target/tgt-payments/mcp\",
\"credentialProviderConfigurations\": [{
\"credentialProviderType\": \"IAM\",
\"credentialProvider\": {
\"iamCredentialProvider\": {
\"roleArn\": \"arn:aws:iam::$(aws sts get-caller-identity --query Account --output text):role/RegistrySyncRole\",
\"service\": \"bedrock-agentcore\"
}
}
}]
}
}" \
--region $REGIONPattern 2: Registry + Identity (Credential-Aware Discovery)
Use Identity service credentials when syncing from protected external sources.
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Identity │ provide │ Registry │ sync │ External │
│ Service │ ────────> │ Sync Engine │ ────────> │ MCP Server │
│ (OAuth cred) │ │ │ │ (protected) │
└──────────────┘ └──────────────┘ └──────────────┘Setup
# 1. Create OAuth credential in Identity service
aws bedrock-agentcore-control create-oauth-credential-provider \
--name "partner-api-oauth" \
--credential-provider-vendor CUSTOM \
--oauth-discovery '{"discoveryUrl": "https://auth.partner.com/.well-known/openid-configuration"}' \
--credential-provider-auth-parameters '{
"oauthParameters": {
"oauthClientId": "registry-sync-client",
"oauthClientSecret": "<CLIENT_SECRET>"
}
}' \
--region us-east-1
# 2. Create synced record using the credential
aws bedrock-agentcore-control create-registry-record \
--registry-id <REGISTRY_ID> \
--name "partner/logistics-server" \
--descriptor-type MCP \
--synchronization-type URL \
--synchronization-configuration '{
"fromUrl": {
"url": "https://api.partner.com/mcp",
"credentialProviderConfigurations": [{
"credentialProviderType": "OAUTH",
"credentialProvider": {
"oauthCredentialProvider": {
"providerArn": "arn:aws:bedrock-agentcore:us-east-1:<account-id>:oauth-credential-provider/partner-api-oauth",
"grantType": "CLIENT_CREDENTIALS"
}
}
}]
}
}' \
--region us-east-1Pattern 3: Registry + Runtime (Dynamic Agent Composition)
Agents running in Runtime can query the registry to dynamically discover and invoke other agents or tools.
┌──────────────┐ invoke ┌──────────────┐ search ┌──────────────┐
│ User │ ────────> │ Orchestrator │ ────────> │ Agent │
│ │ │ Agent │ │ Registry │
└──────────────┘ │ (Runtime) │ └──────┬───────┘
└──────┬───────┘ │
│ discover│
│ invoke │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ Discovered │ <──────── │ Agent Card │
│ Agent │ A2A │ (from record)│
└──────────────┘ └──────────────┘Agent Code Example
import boto3
import json
agentcore_client = boto3.client("bedrock-agentcore")
agentcore_control = boto3.client("bedrock-agentcore-control")
REGISTRY_ARN = "arn:aws:bedrock-agentcore:us-east-1:<account-id>:registry/reg-abc123"
def discover_and_delegate(user_query: str) -> dict:
"""Orchestrator agent discovers relevant agents/tools at runtime."""
# 1. Search registry for relevant capabilities
results = agentcore_client.search_registry_records(
searchQuery=user_query,
registryIds=[REGISTRY_ARN],
maxResults=5
)
# 2. Filter for agents or MCP servers
for record in results.get("records", []):
if record["descriptorType"] == "A2A":
# Parse A2A agent card
agent_card = json.loads(
record["descriptors"]["a2a"]["agentCard"]["inlineContent"]
)
# Invoke the discovered agent via its endpoint
return invoke_a2a_agent(agent_card["url"], user_query)
elif record["descriptorType"] == "MCP":
# Parse MCP server definition
mcp_def = json.loads(
record["descriptors"]["mcp"]["server"]["inlineContent"]
)
# Use the discovered tools
return invoke_mcp_tools(mcp_def, user_query)
return {"error": "No relevant agents or tools found"}Pattern 4: Multi-Registry Architecture
Use multiple registries for different purposes, environments, or access levels.
┌─────────────────────────────────────────────────────┐
│ Organization │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌────────────┐│
│ │ Dev Registry │ │ Prod Registry│ │ Partner ││
│ │ (auto-approve)│ │ (manual) │ │ Registry ││
│ │ │ │ │ │ (JWT auth) ││
│ │ - prototypes │ │ - validated │ │ - external ││
│ │ - experiments│ │ - production │ │ - partner ││
│ │ - draft tools│ │ - curated │ │ - shared ││
│ └──────────────┘ └──────────────┘ └────────────┘│
│ │
│ Promotion flow: Dev ──> Prod ──> Partner │
└─────────────────────────────────────────────────────┘Promoting Records Across Registries
# 1. Read record from dev registry
RECORD=$(aws bedrock-agentcore-control get-registry-record \
--registry-id $DEV_REGISTRY_ID \
--record-id $RECORD_ID \
--region us-east-1)
# 2. Extract descriptor and create in prod registry
DESCRIPTORS=$(echo "$RECORD" | jq '.descriptors')
NAME=$(echo "$RECORD" | jq -r '.name')
DESC=$(echo "$RECORD" | jq -r '.description')
TYPE=$(echo "$RECORD" | jq -r '.descriptorType')
aws bedrock-agentcore-control create-registry-record \
--registry-id $PROD_REGISTRY_ID \
--name "$NAME" \
--description "$DESC" \
--descriptor-type "$TYPE" \
--descriptors "$DESCRIPTORS" \
--record-version "1.0.0" \
--region us-east-1
# 3. Submit for prod approval
aws bedrock-agentcore-control submit-registry-record-for-approval \
--registry-id $PROD_REGISTRY_ID \
--record-id $NEW_RECORD_ID \
--region us-east-1Security Considerations
Cross-Service IAM Policy
A role that can discover resources and deploy them to Gateway:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "RegistrySearch",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:SearchRegistryRecords",
"bedrock-agentcore:InvokeRegistryMcp"
],
"Resource": "arn:aws:bedrock-agentcore:*:*:registry/*"
},
{
"Sid": "RegistryRead",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:GetRegistryRecord",
"bedrock-agentcore:ListRegistryRecords"
],
"Resource": "arn:aws:bedrock-agentcore:*:*:registry/*"
},
{
"Sid": "GatewayDeploy",
"Effect": "Allow",
"Action": [
"bedrock-agentcore:CreateGatewayTarget",
"bedrock-agentcore:GetGatewayTarget"
],
"Resource": "arn:aws:bedrock-agentcore:*:*:gateway/*"
}
]
}Least Privilege by Persona
| Persona | Registry Permissions | Cross-Service Permissions |
|---|---|---|
| Consumer | SearchRegistryRecords, InvokeRegistryMcp | None required |
| Publisher | + CreateRegistryRecord, SubmitRegistryRecordForApproval | None required |
| Curator | + UpdateRegistryRecordStatus | None required |
| Platform Engineer | + All Registry operations | Gateway, Runtime, Identity operations |
Related
- Registry Overview
- Credential Management
- Gateway Service
- Identity Service
- Runtime Service
Cross-Service: Security & Resource-Based Policies
Applies to: Runtime, Gateway, Memory
Overview
Resource-based policies allow attaching IAM-style policies directly to AgentCore resources to control which principals (AWS accounts, IAM users, or roles) can invoke and manage them. They work in conjunction with identity-based IAM policies.
Identity-Based vs Resource-Based Policies
| Aspect | Identity-Based Policy | Resource-Based Policy |
|---|---|---|
| Attachment | Attached to IAM users, roles, or groups | Attached directly to AgentCore resources |
| Management | Managed through AWS IAM | Managed through AgentCore APIs |
| Specifies | Actions and Resources (Principal is implicit) | Principals, Actions, and Conditions (Resource is implicit) |
| Use Case | Define what an identity can do | Define who can access a resource |
Policy Evaluation Matrix
When a request is made, AWS evaluates both identity-based and resource-based policies:
| IAM Policy | Resource Policy | Result |
|---|---|---|
| Grants access | Silent | Allowed |
| Grants access | Grants access | Allowed |
| Grants access | Denies access | Denied |
| Silent | Grants access | Allowed |
| Silent | Silent | Denied |
| Denies access | Any | Denied |
Key principles:
- Explicit Deny always wins — if any policy denies, access is denied regardless
- Either policy can allow — if either permits and none denies, access is granted
- Default Deny — if no policy explicitly permits, access is denied
Supported Resources
| Resource | Supported Actions |
|---|---|
| Agent Runtime | Invoke, invoke for user, invoke command, WebSocket, stop session, get agent card |
| Agent Endpoint | Same as Runtime (hierarchical authorization) |
| Gateway | Invoke gateway |
| Memory | CRUD operations on memory, events, records, sessions, actors, extraction jobs |
Hierarchical Authorization (Runtime + Endpoint)
Agent endpoints are addressable access points to specific runtime versions. When authorizing invocations, AWS evaluates policies for both the runtime and the endpoint.
For cross-account access, create resource-based policies on both resources:
Policy for Agent Runtime (attached to arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<caller-account-id>:role/CrossAccountRole"
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID"
}
]
}Policy for Agent Endpoint (attached to arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID/endpoint/ENDPOINTID):
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<caller-account-id>:role/CrossAccountRole"
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID/endpoint/ENDPOINTID"
}
]
}Important: If either resource denies access or lacks an explicit allow, the request is denied.
Authentication Type Considerations
| Auth Type | Principal Element | Notes |
|---|---|---|
| SigV4 | Specific AWS principals ("AWS": "arn:aws:iam::...") | Evaluated with caller's IAM permissions |
| OAuth | Must use wildcard ("Principal": "*") | JWT tokens validated by Identity Service before policy evaluation; use condition keys to restrict |
Constraint: A Runtime or Gateway supports only one auth type (SigV4 OR OAuth), set at creation time.
IAM Action Reference
Agent Runtime Actions
| Action | Description |
|---|---|
bedrock-agentcore:InvokeAgentRuntime | Invoke an agent runtime |
bedrock-agentcore:InvokeAgentRuntimeForUser | Invoke with user ID header |
bedrock-agentcore:InvokeAgentRuntimeCommand | Execute a shell command in active session |
bedrock-agentcore:InvokeAgentRuntimeWithWebSocketStream | Invoke with WebSocket stream |
bedrock-agentcore:InvokeAgentRuntimeWithWebSocketStreamForUser | WebSocket with user ID header |
bedrock-agentcore:StopRuntimeSession | Stop an active runtime session |
bedrock-agentcore:GetAgentCard | Retrieve agent card information |
Gateway Actions
| Action | Description |
|---|---|
bedrock-agentcore:InvokeGateway | Invoke a gateway |
Memory Actions
| Action | Description |
|---|---|
bedrock-agentcore:GetMemory | Retrieve a Memory resource |
bedrock-agentcore:UpdateMemory | Update a Memory resource |
bedrock-agentcore:DeleteMemory | Delete a Memory resource |
bedrock-agentcore:CreateEvent | Create an event |
bedrock-agentcore:GetEvent | Retrieve an event |
bedrock-agentcore:DeleteEvent | Delete an event |
bedrock-agentcore:ListEvents | List events |
bedrock-agentcore:ListActors | List actors |
bedrock-agentcore:ListSessions | List sessions |
bedrock-agentcore:GetMemoryRecord | Get a memory record |
bedrock-agentcore:ListMemoryRecords | List memory records |
bedrock-agentcore:RetrieveMemoryRecords | Search memory records |
bedrock-agentcore:DeleteMemoryRecord | Delete a memory record |
bedrock-agentcore:BatchCreateMemoryRecords | Batch create records |
bedrock-agentcore:BatchUpdateMemoryRecords | Batch update records |
bedrock-agentcore:BatchDeleteMemoryRecords | Batch delete records |
bedrock-agentcore:StartMemoryExtractionJob | Start extraction job |
bedrock-agentcore:ListMemoryExtractionJobs | List extraction jobs |
Policy Examples
Cross-Account Access
Grant specific roles in another account access to invoke a runtime:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": [
"arn:aws:iam::<caller-account-id>:role/DeveloperRole",
"arn:aws:iam::<caller-account-id>:role/AdminRole"
]
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID"
}
]
}IP Address Restriction
Block traffic from specific IP ranges:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<caller-account-id>:role/ApplicationRole"
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID"
},
{
"Effect": "Deny",
"Principal": {
"AWS": "arn:aws:iam::<caller-account-id>:role/ApplicationRole"
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID",
"Condition": {
"IpAddress": {
"aws:SourceIp": ["192.0.2.0/24", "198.51.100.0/24"]
}
}
}
]
}VPC Restriction
Allow traffic only from a specific VPC:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<caller-account-id>:role/ApplicationRole"
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID"
},
{
"Effect": "Deny",
"Principal": {
"AWS": "arn:aws:iam::<caller-account-id>:role/ApplicationRole"
},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID",
"Condition": {
"StringNotEquals": {
"aws:SourceVpc": "vpc-1a2b3c4d"
}
}
}
]
}OAuth with VPC Restriction
For OAuth-authenticated resources, use wildcard principal with condition keys:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOAuthFromVPC",
"Effect": "Allow",
"Principal": "*",
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": "arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID",
"Condition": {
"StringEquals": {
"aws:SourceVpc": "vpc-1a2b3c4d"
}
}
}
]
}Note: Wildcard principal is required for OAuth. JWT tokens are validated by Identity Service before policy evaluation. Anonymous requests are rejected before reaching policy evaluation.
Managing Resource Policies
AWS CLI
# Create or update a resource policy
aws bedrock-agentcore-control put-resource-policy \
--resource-arn arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID \
--policy file://policy.json
# Get a resource policy
aws bedrock-agentcore-control get-resource-policy \
--resource-arn arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID
# Delete a resource policy
aws bedrock-agentcore-control delete-resource-policy \
--resource-arn arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTIDPython SDK
import boto3
import json
client = boto3.client('bedrock-agentcore-control', region_name='us-west-2')
resource_arn = 'arn:aws:bedrock-agentcore:us-west-2:<resource-owner-account-id>:runtime/AGENTID'
# Put resource policy
policy = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::<caller-account-id>:role/MyRole"},
"Action": "bedrock-agentcore:InvokeAgentRuntime",
"Resource": resource_arn
}
]
}
client.put_resource_policy(
resourceArn=resource_arn,
policy=json.dumps(policy)
)
# Get resource policy
response = client.get_resource_policy(resourceArn=resource_arn)
print(response['policy'])
# Delete resource policy
client.delete_resource_policy(resourceArn=resource_arn)Important: TheResourcefield must contain the exact ARN of the resource the policy is attached to. Using"Resource": "*"results in a validation error.
Best Practices
- Least privilege — grant only the specific actions needed
- Use conditions — restrict by VPC, IP, or source account even when allowing access
- Audit regularly — review resource policies alongside identity-based policies
- Cross-account requires both — for Runtime, create policies on both the runtime AND endpoint
- OAuth requires wildcard — use condition keys (
aws:SourceVpc,aws:SourceVpce) to restrict - *No `Resource: `** — always use the exact resource ARN
Related
- Credential Management
- Registry Integration
- Runtime Service
- Gateway Service
- Memory Service
- Security in AgentCore (AWS Docs)
- IAM Policy Examples (AWS Docs)
AgentCore OAuth Integration Guide
Detailed explanation of Amazon Bedrock AgentCore's three-layer OAuth authentication architecture: Inbound JWT (caller verification), Outbound Credential Provider (proxied user access to third-party APIs), and Gateway OAuth (transparent credential injection for MCP tools).
Table of Contents
- 1. Three-Layer OAuth Architecture and WAT Chain
- 1.1 Overview
- 1.2 Workload Access Token (WAT) Chain
- 1.3 Three-Layer Comparison
- 2. Inbound JWT Authorizer - Caller Verification
- 2.1 Configuration
- 2.2 How It Works
- 2.3 Container Obtains User Identity
- 2.4 Cognito JWT Structure Reference
- 2.5 Caller Bearer Token
- 2.6 Unauthenticated 401 Response
- 3. Outbound Credential Provider - Proxied User Access to Third-Party APIs
- 3.1 Core Concept
- 3.2 Two OAuth Modes
- 3.3 Registering a Credential Provider
- 3.4 Agent Code Integration
- 3.5 Complete OAuth Flow
- 3.6 IAM Permissions
- 4. Gateway OAuth - Transparent Credential Injection for MCP Tools
- 4.1 Core Concept
- 4.2 Workflow
- 4.3 Gateway Creation
- 4.4 Comparison: Gateway vs Agent Code OAuth
- 4.5 credentialProviderConfigurations Types
- 4.6 Pre-Integrated Services
- 5. Cognito Complete Configuration Reference
- 6. Supported IdP List
- 7. Selection Guide
- 8. Security Essentials
- Appendix A: End-to-End Practical Example
Quick Navigation
| You want to learn about... | Jump to |
|---|---|
| Three-layer architecture overview + how WAT chains together | Section 1 |
| How to verify caller identity | Section 2 |
| How to proxy user access to third-party APIs | Section 3 |
| Gateway MCP tools' transparent credential injection | Section 4 |
| Cognito configuration reference | Section 5 |
| Which IdPs are supported | Section 6 |
| Which approach to choose | Section 7 |
| Security essentials | Section 8 |
| End-to-end practical code (CDK + Container + Lambda) | Appendix A |
This document is based on the AgentCore API and SDK as of mid-2025. API parameters and SDK import paths may change with version updates. Please refer to the official documentation as the authoritative source.
---
1. Three-Layer OAuth Architecture and WAT Chain
1.1 Overview
Caller (Portal / CLI / API)
|
Bearer JWT Token
|
v
+--- AgentCore Platform Layer ---+
| |
| Inbound JWT Authorizer | <-- Layer 1: Verify caller identity
| (OIDC Discovery + |
| JWT signature/claims check) |
| |
+--------+-----------------------+
| Verification passed
v
+--- Agent Container ------------+
| |
| /invocations endpoint |
| Obtain user_id: |
| Request body or JWT claims |
| |
+--------+-----------------------+
| Agent needs to call
| third-party API on
| behalf of user
v
+--- AgentCore Identity ---------+
| |
| Outbound Credential Provider | <-- Layer 2: Proxy user access
| (Token Vault stores | to third-party APIs
| per-user OAuth tokens) |
| |
+--------+-----------------------+
| OR
v
+--- AgentCore Gateway ----------+
| |
| Gateway OAuth | <-- Layer 3: Transparent credential
| (Auto-inject OAuth token | injection for MCP tools
| into MCP tool calls) |
| |
+--------------------------------+Three layers, progressive enhancement:
| Layer | Component | Solves | Code Impact |
|---|---|---|---|
| 1 | Inbound JWT Authorizer | "Who is the caller?" | Zero (platform-level) |
| 2 | Outbound Credential Provider | "How does Agent access third-party APIs on behalf of user?" | Agent code calls @requires_access_token |
| 3 | Gateway OAuth | "How do MCP tools transparently inject credentials?" | Zero (Gateway handles it) |
1.2 Workload Access Token (WAT) Chain
WAT is the core token connecting the three layers. When inbound JWT is enabled, AgentCore platform issues a WAT to the container, representing "which workload (Runtime) + which user (JWT sub)":
Caller JWT --> AgentCore Platform verifies --> Issues WAT to container
|
v
WAT = workload_id + user_id
|
+---------------------------+---------------------------+
| |
Outbound Credential Provider Gateway OAuth
WAT identifies user --> WAT identifies user -->
Token Vault returns Gateway auto-injects
user's third-party token third-party tokenKey: WAT doesn't contain the third-party OAuth token itself. It serves as a "lookup key" — the Token Vault stores per-(workload, user) third-party tokens, and WAT is used to find the corresponding token.
1.3 Three-Layer Comparison
| Dimension | Inbound JWT | Outbound Credential Provider | Gateway OAuth |
|---|---|---|---|
| Direction | Inbound (caller -> Agent) | Outbound (Agent -> third-party) | Outbound (MCP tool -> third-party) |
| Purpose | Verify caller identity | Proxy user access to third-party API | Transparent credential injection for MCP tools |
| Code impact | Zero | @requires_access_token decorator | Zero |
| Token storage | Caller holds JWT | Token Vault (per-user) | Token Vault (per-user) |
| Typical IdP | Cognito, Okta, Auth0 | Google, Slack, Jira, etc. | Same as Outbound |
| Dependency | Independent | Requires Layer 1 (needs WAT to identify user) | Requires Layer 1 (needs WAT to identify user) |
---
2. Inbound JWT Authorizer — Caller Verification
2.1 Configuration
When creating a Runtime, configure authorizerConfiguration to enable Inbound JWT verification:
response = client.create_agent_runtime(
agentRuntimeName='my-agent',
authorizerConfiguration={
"customJWTAuthorizer": {
# OIDC Discovery endpoint, AgentCore auto-fetches JWKS
"discoveryUrl": "https://cognito-idp.us-east-1.amazonaws.com/POOL_ID/.well-known/openid-configuration",
# Allowed client IDs (matches JWT aud or client_id claim)
"allowedClients": ["your-cognito-client-id"],
# (Optional) Custom claims validation
"customClaims": {
"scope": "openid profile"
}
}
},
...
)Key parameters:
| Parameter | Required | Description |
|---|---|---|
discoveryUrl | Yes | OIDC Discovery URL, AgentCore auto-fetches public keys for JWT signature verification |
allowedClients | Yes | List of allowed client IDs. JWT's aud or client_id claim must match one of these |
customClaims | No | Custom claims validation. Example: require specific scope or role |
2.2 How It Works
Caller sends: Authorization: Bearer <JWT>
|
v
AgentCore Platform Layer:
1. Fetch JWKS from discoveryUrl (cached)
2. Verify JWT signature
3. Verify exp (not expired), iss (issuer match)
4. Verify aud/client_id in allowedClients
5. (If configured) Verify customClaims
|
Verification passed
|
v
6. Issue Workload Access Token (WAT) to container
7. Forward request to container /invocations endpointImportant: With JWT Authorizer enabled, callers cannot use boto3.invoke_agent_runtime() (SigV4). They must send HTTPS requests directly with Authorization: Bearer {JWT}.
2.3 Container Obtains User Identity
After JWT verification passes, the container obtains the user identity through one of three methods:
Method A: Extract from request body (simplest)
The caller includes user_id in the request body. AgentCore platform has already verified identity at the platform layer, the container just reads it:
# [FastAPI custom build]
@app.post("/invocations")
async def invocations(request: ChatRequest):
user_id = request.user_id # Passed by caller, platform layer already verified
session_id = request.id
...Method B: Decode JWT claims (when additional claims like scope/role are needed)
Requires configuring --request-header-allowlist "Authorization" when creating the Runtime so the original JWT header is passed through to the container:
# [FastAPI custom build]
import jwt
@app.post("/invocations")
async def invocations(request: Request):
auth_header = request.headers.get("Authorization", "")
if auth_header.startswith("Bearer "):
# No need to verify signature — platform layer already verified
claims = jwt.decode(
auth_header[7:],
options={"verify_signature": False}
)
user_id = claims.get("sub")
scopes = claims.get("scope", "").split()
email = claims.get("email")2.4 Cognito JWT Structure Reference
A typical Cognito access_token decoded:
{
"sub": "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111",
"iss": "https://cognito-idp.us-east-1.amazonaws.com/us-east-1_EXAMPLE",
"client_id": "your-cognito-client-id",
"origin_jti": "...",
"event_id": "...",
"token_use": "access",
"scope": "openid profile",
"auth_time": 1752275688,
"exp": 1752279288,
"iat": 1752275688,
"jti": "...",
"username": "testuser@example.com"
}2.5 Caller Bearer Token
Method 1: Authorization Code Flow + PKCE (recommended for browser/mobile apps)
// Frontend TypeScript example
import { CognitoIdentityProviderClient, InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider';
// 1. Redirect user to Cognito Hosted UI
const authUrl = `https://${cognitoDomain}/oauth2/authorize?`
+ `client_id=${clientId}`
+ `&response_type=code`
+ `&redirect_uri=${encodeURIComponent(redirectUri)}`
+ `&scope=openid+profile`
+ `&code_challenge=${codeChallenge}`
+ `&code_challenge_method=S256`;
// 2. After redirect back, exchange code for token
const tokenResponse = await fetch(`https://${cognitoDomain}/oauth2/token`, {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: clientId,
code: authorizationCode,
redirect_uri: redirectUri,
code_verifier: codeVerifier, // PKCE
}),
});
const { access_token } = await tokenResponse.json();
// Use access_token to call AgentCore RuntimeMethod 2: CLI quick test
# Get token (Resource Owner Password Flow — for testing only, not recommended for production)
TOKEN=$(curl -s -X POST "https://cognito-idp.us-east-1.amazonaws.com/" \
-H "Content-Type: application/x-amz-json-1.1" \
-H "X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth" \
-d '{
"AuthFlow": "USER_PASSWORD_AUTH",
"ClientId": "YOUR_CLIENT_ID",
"AuthParameters": {
"USERNAME": "testuser@example.com",
"PASSWORD": "TestPassword123!"
}
}' | jq -r '.AuthenticationResult.AccessToken')
# Call AgentCore Runtime
ESCAPED_ARN=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$AGENT_ARN', safe=''))")
curl -N \
"https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/${ESCAPED_ARN}/invocations?qualifier=DEFAULT" \
-H "Authorization: Bearer ${TOKEN}" \
-H "Content-Type: application/json" \
-H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: test-session-001" \
-d '{"id":"test-session-001","user_id":"testuser@example.com","messages":[{"role":"user","content":"Hello"}]}'2.6 Unauthenticated 401 Response
When a caller sends a request without a valid JWT, AgentCore returns:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{ESCAPED_ARN}/invocations/.well-known/oauth-protected-resource?qualifier={QUALIFIER}"The client can use the resource_metadata URL to discover OAuth endpoints and automatically obtain a token.
---
3. Outbound Credential Provider — Proxied User Access to Third-Party APIs
3.1 Core Concept
When an Agent needs to call third-party APIs (Google Drive, Slack, Jira, etc.) on behalf of the user, Outbound Credential Provider manages the complete OAuth lifecycle:
User authorizes (one-time) --> Token Vault stores refresh_token + access_token
|
Agent needs third-party API --> @requires_access_token -->
Token Vault returns valid access_token (auto-refresh if expired)
|
Agent code receives access_token --> Call third-party APICore advantage: Agent code never touches refresh_token or client_secret. Token Vault handles storage, refresh, and rotation.
3.2 Two OAuth Modes
| Mode | Applicable Scenario | OAuth Flow |
|---|---|---|
| USER_FEDERATION | User has their own third-party account (e.g., user's personal Google Drive) | Authorization Code Flow: user authorizes in browser, Token Vault stores per-user token |
| SERVICE_ACCOUNT | Shared service account (e.g., company-wide Slack Bot) | Client Credentials Flow: no user authorization needed, single shared token |
3.3 Registering a Credential Provider
Example: Google OAuth (USER_FEDERATION)
import boto3
client = boto3.client('bedrock-agentcore-control')
response = client.create_credential_provider(
name="google-provider",
credentialProviderType="OAUTH",
oauthCredentialProvider={
"providerType": "GOOGLE",
"providerUrl": "https://accounts.google.com",
"clientId": "your-google-client-id.apps.googleusercontent.com",
"clientSecret": "your-google-client-secret",
"scopes": ["https://www.googleapis.com/auth/drive.metadata.readonly"],
"authFlow": "USER_FEDERATION",
}
)Example: Cognito (SERVICE_ACCOUNT)
response = client.create_credential_provider(
name="cognito-m2m-provider",
credentialProviderType="OAUTH",
oauthCredentialProvider={
"providerType": "COGNITO",
"providerUrl": "https://cognito-idp.us-east-1.amazonaws.com/POOL_ID",
"clientId": "cognito-client-id",
"clientSecret": "cognito-client-secret",
"scopes": ["my-resource-server/read"],
"authFlow": "SERVICE_ACCOUNT",
}
)Example: API Key
response = client.create_credential_provider(
name="my-api-key-provider",
credentialProviderType="API_KEY",
apiKeyCredentialProvider={
"apiKey": "sk-your-api-key-here",
}
)3.4 Agent Code Integration
Use the @requires_access_token decorator, which auto-injects the access_token parameter:
from bedrock_agentcore.identity.auth import requires_access_token
@requires_access_token(
provider_name="google-provider",
scopes=["https://www.googleapis.com/auth/drive.metadata.readonly"],
auth_flow="USER_FEDERATION",
on_auth_url=lambda url: print("Please authorize:", url),
)
async def list_drive_files(*, access_token: str):
"""List user's Google Drive files."""
# access_token is auto-injected, Agent never touches refresh_token / client_secret
import requests
response = requests.get(
"https://www.googleapis.com/drive/v3/files",
headers={"Authorization": f"Bearer {access_token}"},
params={"pageSize": 10}
)
return response.json()3.5 Complete OAuth Flow
1. Agent calls @requires_access_token decorated function
|
2. SDK checks Token Vault: does this (workload, user) have a valid token?
|
+---+---+
| |
Yes No (first time or expired)
| |
| 3a. If refresh_token exists and access_token expired:
| SDK auto-refreshes --> new access_token
| |
| 3b. If no token at all (first authorization):
| SDK calls on_auth_url callback --> returns authorization URL
| User opens URL in browser --> authorizes --> callback receives code
| SDK exchanges code for tokens --> stores in Token Vault
| |
4. SDK returns valid access_token to decorated function
|
5. Function uses access_token to call third-party API3.6 IAM Permissions
The AgentCore Runtime role needs the following permissions:
{
"Effect": "Allow",
"Action": [
"bedrock-agentcore:GetCredentialProvider",
"bedrock-agentcore:GetTokenVaultToken",
"bedrock-agentcore:PutTokenVaultToken",
"bedrock-agentcore:RefreshTokenVaultToken"
],
"Resource": "arn:aws:bedrock-agentcore:*:*:credential-provider/*"
}---
4. Gateway OAuth — Transparent Credential Injection for MCP Tools
4.1 Core Concept
Gateway OAuth takes Outbound Credential Provider a step further: credentials are injected at the Gateway level, completely transparent to Agent code and LLM. The Agent just calls MCP tools normally; the Gateway automatically handles OAuth.
4.2 Workflow
Agent calls MCP tool (e.g., "list Jira issues")
|
MCPClient --> Gateway /mcp endpoint
|
Gateway:
1. Verify caller JWT (Inbound)
2. Route to target MCP tool
3. Check credentialProviderConfigurations:
- GATEWAY_IAM_ROLE: use Gateway's own IAM role (for Lambda targets)
- OAUTH: get user's third-party token from Token Vault via WAT
- API_KEY: get API key from credential provider
4. Inject credentials into downstream request headers
|
Target (Lambda / MCP Server) receives request with injected credentials4.3 Gateway Creation
import boto3
client = boto3.client('bedrock-agentcore-control')
response = client.create_gateway(
name="my-gateway",
protocolType="MCP",
description="MCP Gateway with OAuth",
)
# Create target with credential provider configuration
target_response = client.create_gateway_target(
gatewayIdentifier=response["gatewayId"],
name="jira-target",
targetConfiguration={
"mcpTargetConfiguration": {
"openApiSchema": {
"s3": {"uri": "s3://my-bucket/jira-openapi.json"}
},
}
},
credentialProviderConfigurations=[
{
"credentialProviderType": "OAUTH",
"credentialProvider": {
"credentialProviderArn": "arn:aws:bedrock-agentcore:...:credential-provider/jira-provider"
}
}
],
)4.4 Comparison: Gateway vs Agent Code OAuth
| Dimension | Gateway OAuth | Agent Code (@requires_access_token) |
|---|---|---|
| Code impact | Zero — Gateway handles everything | Agent code adds decorator |
| LLM visibility | Token completely invisible to LLM | access_token visible in tool function |
| Applicable scope | MCP tools accessed through Gateway | Any third-party API |
| Configuration | credentialProviderConfigurations on Gateway Target | @requires_access_token in Python |
| Flexibility | Standardized process, less flexible | Full control over OAuth flow |
| Recommended for | MCP tool scenarios | Non-MCP scenarios, or when custom flow is needed |
4.5 credentialProviderConfigurations Types
| Type | Description | Use Case |
|---|---|---|
| GATEWAY_IAM_ROLE | Gateway uses its own IAM role to invoke target (e.g., Lambda) | Lambda targets, AWS service targets |
| OAUTH | Gateway uses WAT to get user's third-party OAuth token from Token Vault | Third-party SaaS (Jira, Slack, Salesforce) |
| API_KEY | Gateway uses API key from credential provider | API key-authenticated services |
4.6 Pre-Integrated Services
Gateway provides 1-Click integration for the following services:
| Category | Services |
|---|---|
| CRM | Salesforce |
| Collaboration | Slack, Asana |
| Project Management | Jira, Zendesk |
These pre-integrated services have built-in OAuth flows. For other OAuth 2.0-compatible services, use the generic OAuth credential provider.
---
5. Cognito Complete Configuration Reference
5.1 User Pool Setup
// CDK TypeScript
import * as cognito from 'aws-cdk-lib/aws-cognito';
const userPool = new cognito.UserPool(this, 'AgentUserPool', {
userPoolName: 'agentcore-users',
selfSignUpEnabled: true,
signInAliases: { email: true },
autoVerify: { email: true },
passwordPolicy: {
minLength: 8,
requireUppercase: true,
requireDigits: true,
requireSymbols: false,
},
accountRecovery: cognito.AccountRecovery.EMAIL_ONLY,
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
// Cognito Domain (required for Hosted UI)
userPool.addDomain('CognitoDomain', {
cognitoDomain: { domainPrefix: 'my-agent-auth' },
});5.2 App Client (Authorization Code + PKCE)
const userPoolClient = userPool.addClient('AgentClient', {
userPoolClientName: 'agent-app-client',
generateSecret: false, // Public client (SPA/mobile)
authFlows: {
userPassword: true, // For CLI testing
userSrp: true, // Secure Remote Password
},
oAuth: {
flows: {
authorizationCodeGrant: true, // Authorization Code + PKCE
},
scopes: [cognito.OAuthScope.OPENID, cognito.OAuthScope.PROFILE],
callbackUrls: ['http://localhost:3000/callback'],
logoutUrls: ['http://localhost:3000/'],
},
supportedIdentityProviders: [
cognito.UserPoolClientIdentityProvider.COGNITO,
],
accessTokenValidity: cdk.Duration.hours(1),
idTokenValidity: cdk.Duration.hours(1),
refreshTokenValidity: cdk.Duration.days(30),
});5.3 CLI Quick Test
# 1. Create test user
aws cognito-idp sign-up \
--client-id YOUR_CLIENT_ID \
--username testuser@example.com \
--password TestPassword123! \
--user-attributes Name=email,Value=testuser@example.com
# 2. Confirm user (skip email verification for testing)
aws cognito-idp admin-confirm-sign-up \
--user-pool-id YOUR_POOL_ID \
--username testuser@example.com
# 3. Get token
TOKEN=$(aws cognito-idp initiate-auth \
--client-id YOUR_CLIENT_ID \
--auth-flow USER_PASSWORD_AUTH \
--auth-parameters USERNAME=testuser@example.com,PASSWORD=TestPassword123! \
--query 'AuthenticationResult.AccessToken' --output text)
echo "Access Token: ${TOKEN}"---
6. Supported IdP List
AgentCore Identity supports 25+ OAuth 2.0 providers:
| Category | Providers |
|---|---|
| Enterprise | Cognito, Okta, Auth0, Azure AD, Ping Identity |
| Social | Google, GitHub, Facebook, Apple, X (Twitter) |
| SaaS | Salesforce, Slack, Jira, Asana, Zendesk, HubSpot, Shopify, Zoom, Dropbox, Box, Twitch, Spotify, LinkedIn |
| Custom | Any OAuth 2.0 / OIDC-compatible provider |
---
7. Selection Guide
7.1 Scenario Comparison
| Scenario | Recommended Approach |
|---|---|
| Agent only calls your own backend API | Inbound JWT + API Key (Secrets Manager) |
| Agent calls third-party OAuth API on behalf of user | Inbound JWT + Outbound Credential Provider |
| Agent accesses third-party services through MCP tools | Inbound JWT + Gateway OAuth (zero code changes in Agent) |
7.2 Evolution Path
Stage 1: Simple Authentication
Inbound JWT + API Key (Secrets Manager)
Suitable for: Agent only calls your own backend
|
v
Stage 2: Third-Party Integration
+ Outbound Credential Provider
Suitable for: Agent needs to call Slack/Jira/Google on behalf of user
|
v
Stage 3: Full MCP Toolchain
+ Gateway OAuth
Suitable for: Standardized MCP tools + transparent credential injection7.3 Container Code Architecture Patterns
Both local tool mode and Gateway MCP mode can coexist — the same Agent can register both local tools and Gateway MCP tools:
Agent Container
|
+-- Local tools (@tool functions)
| Call backend API via HTTP
| Credentials: API Key from Secrets Manager
|
+-- Gateway MCP tools (via MCPClient)
Connect to Gateway /mcp endpoint
Credentials: Gateway auto-injects OAuth7.4 WAT Retrieval Methods
| Method | Description | Recommended |
|---|---|---|
| Manual retrieval | Directly call AgentCore Identity API | For debugging |
| SDK-managed | @requires_access_token or Gateway auto-handling | Yes (production) |
See Appendix A.7 for complete code examples.
---
8. Security Essentials
8.1 Credential Isolation
| Layer | Isolation Guarantee |
|---|---|
| Inbound JWT | Platform-level verification, container doesn't need to validate JWT signatures |
| Outbound OAuth | Agent code only receives access_token, never refresh_token or client_secret |
| Gateway | Agent code and LLM have zero visibility into credentials |
8.2 LLM Security
OAuth tokens never appear in:
- System prompts
- Tool input/output
- Conversation history
Even if the LLM is prompt-injected, it cannot leak credentials.
8.3 Audit Logging
| Dimension | Logged Information |
|---|---|
| Who | JWT sub (user identity) |
| What | Which third-party API, which scope |
| When | Timestamp |
| Result | Success/failure, error details |
---
Appendix A: End-to-End Practical Example — AgentCore Runtime + Gateway MCP + Lambda
A.1 Architecture Overview
Frontend User (Browser/CLI)
|
| Bearer JWT Token
v
AgentCore Runtime (MicroVM)
+------------------------------------------+
| Agent Container |
| +-- Strands Agent |
| +-- Local tools (backend API) |
| +-- MCPClient --> Gateway |
| | |
| v |
| Lambda Target |
| (Business logic) |
+------------------------------------------+A.2 CDK Cognito Authentication Setup
See scripts/ directory for runnable templates. Reference CDK code:
// infrastructure/lib/auth/cognito-construct.ts
import * as cdk from 'aws-cdk-lib';
import * as cognito from 'aws-cdk-lib/aws-cognito';
import { Construct } from 'constructs';
export class CognitoAuthStack extends Construct {
public readonly userPool: cognito.UserPool;
public readonly userPoolClient: cognito.UserPoolClient;
public readonly issuerUrl: string;
constructor(scope: Construct, id: string) {
super(scope, id);
this.userPool = new cognito.UserPool(this, 'UserPool', {
userPoolName: 'agentcore-users',
selfSignUpEnabled: true,
signInAliases: { email: true },
autoVerify: { email: true },
removalPolicy: cdk.RemovalPolicy.DESTROY,
});
this.userPool.addDomain('Domain', {
cognitoDomain: { domainPrefix: 'my-agent-auth' },
});
this.userPoolClient = this.userPool.addClient('AppClient', {
generateSecret: false,
authFlows: { userPassword: true, userSrp: true },
oAuth: {
flows: { authorizationCodeGrant: true },
scopes: [cognito.OAuthScope.OPENID, cognito.OAuthScope.PROFILE],
callbackUrls: ['http://localhost:3000/callback'],
},
accessTokenValidity: cdk.Duration.hours(1),
refreshTokenValidity: cdk.Duration.days(30),
});
this.issuerUrl = `https://cognito-idp.${cdk.Stack.of(this).region}.amazonaws.com/${this.userPool.userPoolId}`;
}
}A.3 CDK Gateway + Lambda Construct
// infrastructure/lib/agentcore/gateway-lambda-construct.ts
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { Construct } from 'constructs';
export interface GatewayLambdaProps {
gatewayName: string;
lambdaCode: lambda.Code;
schemaS3Uri: string;
}
export class AgentCoreGatewayLambdaStack extends Construct {
public readonly gatewayId: string;
constructor(scope: Construct, id: string, props: GatewayLambdaProps) {
super(scope, id);
// IAM Role for Gateway to invoke Lambda
const gatewayRole = new iam.Role(this, 'GatewayRole', {
assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
inlinePolicies: {
InvokeLambda: new iam.PolicyDocument({
statements: [new iam.PolicyStatement({
actions: ['lambda:InvokeFunction'],
resources: ['*'], // Restrict to specific Lambda ARN in production
})],
}),
},
});
// Lambda Target
const targetLambda = new lambda.Function(this, 'TargetLambda', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'index.handler',
code: props.lambdaCode,
timeout: cdk.Duration.seconds(30),
});
// Custom Resource Lambda for Gateway lifecycle management
// See scripts/gateway-custom-resource-lambda.py for implementation
const customResourceLambda = new lambda.Function(this, 'CustomResourceLambda', {
runtime: lambda.Runtime.PYTHON_3_12,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda/gateway-custom-resource'),
timeout: cdk.Duration.minutes(5),
environment: {
GATEWAY_NAME: props.gatewayName,
TARGET_LAMBDA_ARN: targetLambda.functionArn,
OPENAPI_SCHEMA_S3_URI: props.schemaS3Uri,
GATEWAY_IAM_ROLE_ARN: gatewayRole.roleArn,
},
});
// Grant permissions
customResourceLambda.addToRolePolicy(new iam.PolicyStatement({
actions: [
'bedrock-agentcore:CreateGateway',
'bedrock-agentcore:DeleteGateway',
'bedrock-agentcore:GetGateway',
'bedrock-agentcore:CreateGatewayTarget',
'bedrock-agentcore:DeleteGatewayTarget',
'bedrock-agentcore:ListGatewayTargets',
],
resources: ['*'],
}));
}
}A.4 Custom Resource Lambda
See `scripts/gateway-custom-resource-lambda.py` for the complete implementation of the CloudFormation Custom Resource Lambda that manages Gateway create/update/delete lifecycle.
A.5 CDK AgentCore Runtime
// infrastructure/lib/agentcore/runtime-construct.ts
import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as agentcore_cfn from 'aws-cdk-lib/aws-bedrock-agentcore';
import { Construct } from 'constructs';
export interface RuntimeProps {
cognitoIssuer: string;
cognitoClientId: string;
ecrImageUri: string;
gatewayUrl?: string;
memoryId?: string;
}
export class AgentCoreRuntimeCdkStack extends Construct {
public readonly runtimeArn: string;
constructor(scope: Construct, id: string, props: RuntimeProps) {
super(scope, id);
// IAM Role for Runtime
const agentCoreRole = new iam.Role(this, 'RuntimeRole', {
assumedBy: new iam.ServicePrincipal('bedrock-agentcore.amazonaws.com'),
inlinePolicies: {
ECR: new iam.PolicyDocument({
statements: [new iam.PolicyStatement({
actions: ['ecr:GetAuthorizationToken', 'ecr:BatchGetImage', 'ecr:GetDownloadUrlForLayer'],
resources: ['*'],
})],
}),
Bedrock: new iam.PolicyDocument({
statements: [new iam.PolicyStatement({
actions: ['bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream'],
resources: ['*'],
})],
}),
TokenVault: new iam.PolicyDocument({
statements: [new iam.PolicyStatement({
actions: [
'bedrock-agentcore:GetTokenVaultToken',
'bedrock-agentcore:PutTokenVaultToken',
'bedrock-agentcore:RefreshTokenVaultToken',
],
resources: ['*'],
})],
}),
},
});
const cfnRuntime = new agentcore_cfn.CfnRuntime(this, 'Runtime', {
agentRuntimeName: 'my-agent',
agentRuntimeArtifact: {
containerConfiguration: { containerUri: props.ecrImageUri },
},
networkConfiguration: { networkMode: 'PUBLIC' },
protocolConfiguration: 'HTTP',
roleArn: agentCoreRole.roleArn,
authorizerConfiguration: {
customJwtAuthorizer: {
discoveryUrl: `${props.cognitoIssuer}/.well-known/openid-configuration`,
allowedClients: [props.cognitoClientId],
},
},
environmentVariables: {
...(props.gatewayUrl && { MCP_SERVER_URL: props.gatewayUrl }),
...(props.memoryId && { AGENTCORE_MEMORY_ID: props.memoryId }),
AGENT_OBSERVABILITY_ENABLED: 'true',
},
});
this.runtimeArn = cfnRuntime.attrAgentRuntimeArn;
}
}A.6 CDK Assembly Layer
// infrastructure/lib/main-stack.ts
// Wire together: Cognito + Gateway + Runtime
// Runtime and Gateway share the same Cognito App Client
const auth = new CognitoAuthStack(this, 'Auth');
const gateway = new AgentCoreGatewayLambdaStack(this, 'Gateway', {
gatewayName: 'my-gateway',
lambdaCode: lambda.Code.fromAsset('lambda/target'),
schemaS3Uri: 's3://my-bucket/openapi.json',
});
const runtime = new AgentCoreRuntimeCdkStack(this, 'Runtime', {
cognitoIssuer: auth.issuerUrl,
cognitoClientId: auth.userPoolClient.userPoolClientId,
ecrImageUri: '123456789012.dkr.ecr.us-east-1.amazonaws.com/my-agent:latest',
gatewayUrl: `https://${gateway.gatewayId}.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp`,
});A.7 Agent Runtime Container Code
See `scripts/runtime-fastapi-template.py` for a complete FastAPI Runtime template.
Local tool mode (calling backend API directly):
from strands import Agent, tool
from strands.models.bedrock import BedrockModel
@tool
def search_orders(user_id: str, limit: int = 10) -> str:
"""Search recent orders for a user."""
import requests
response = requests.get(
f"{BACKEND_API_URL}/api/orders",
headers={"X-Service-Api-Key": SERVICE_API_KEY},
params={"user_id": user_id, "limit": limit},
)
return response.text
model = BedrockModel(model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0")
agent = Agent(system_prompt="You are a helpful assistant.", model=model, tools=[search_orders])Gateway MCP mode (connecting to Gateway for MCP tools):
import os
from strands import Agent
from strands.tools.mcp import MCPClient
from mcp.client.streamable_http import streamable_http_client
gateway_url = os.environ["MCP_SERVER_URL"]
# e.g., "https://{gatewayId}.gateway.bedrock-agentcore.{region}.amazonaws.com/mcp"
mcp_client = MCPClient(lambda: streamable_http_client(url=gateway_url))
# In startup: create MCPClient (lazy connection)
# In /invocations: pass to Agent
agent = Agent(
system_prompt="You are a helpful assistant.",
model=model,
tools=[*local_tools, mcp_client],
)---
This document is based on AWS official documentation (AgentCore Identity, Gateway, Runtime), mid-2025 AgentCore API and SDK. API parameters, SDK import paths may change with version updates.
AgentCore Runtime Protocol Reference: HTTP, MCP, A2A, AG-UI
AgentCore Runtime supports four communication protocols, each targeting a different interaction pattern. This document, based on AWS official documentation, provides a detailed explanation of each protocol's container contract, endpoint specification, request/response format, and applicable scenarios.
---
Table of Contents
- 1. Protocol Overview
- 2. HTTP Protocol
- 2.1 Container Contract
- 2.2 Endpoint Details
- 2.3 Applicable Scenarios
- 3. MCP Protocol
- 3.1 MCP Protocol Core Concepts
- 3.2 MCP Container Contract in AgentCore
- 3.3 Endpoint Details
- 3.4 Session Management
- 3.5 Code Example
- 3.6 Relationship with AgentCore Gateway
- 3.7 Applicable Scenarios
- 4. A2A Protocol
- 4.1 A2A Protocol Core Concepts
- 4.2 A2A Container Contract in AgentCore
- 4.3 Endpoint Details
- 4.4 Error Handling
- 4.5 Code Example
- 4.6 Applicable Scenarios
- 5. AG-UI Protocol
- 5.1 What Problem Does AG-UI Solve
- 5.2 AG-UI Container Contract in AgentCore
- 5.3 Endpoint Details
- 5.4 AG-UI Event Types
- 5.5 Code Example
- 5.6 Integration with CopilotKit
- 5.7 Applicable Scenarios
- 6. Comparison of the Four Protocols
- 7. How to Choose a Protocol
- 8. Authentication: Common to All Protocols
---
1. Protocol Overview
AgentCore Runtime specifies the protocol at creation time via protocolConfiguration. A single Runtime can only use one protocol:
| Protocol | Purpose | Port | Primary Endpoint | Communication Mode | Creator |
|---|---|---|---|---|---|
| HTTP | General-purpose agent interaction | 8080 | POST /invocations | JSON / SSE / WebSocket | — |
| MCP | Tool and data services | 8000 | POST /mcp | JSON-RPC 2.0 | Anthropic |
| A2A | Inter-agent collaboration | 9000 | POST / | JSON-RPC 2.0 | |
| AG-UI | Agent-to-frontend UI | 8080 | POST /invocations | SSE event stream | Led by CopilotKit |
All four protocols share the same AgentCore infrastructure: MicroVM session isolation, OAuth/SigV4 authentication, and auto-scaling. The difference lies in the communication contract inside the container.
All protocols must implement the `GET /ping` health check endpoint (returning{"status": "Healthy"}or{"status": "HealthyBusy"}). This is a universal requirement of AgentCore Runtime and does not belong to any specific protocol's specification.
Caller -> AgentCore Platform (Auth + Routing) -> Container inside MicroVM
|
|-- HTTP: POST :8080/invocations
|-- MCP: POST :8000/mcp
|-- A2A: POST :9000/
+-- AG-UI: POST :8080/invocationsRegardless of which protocol is chosen, the AgentCore platform endpoint used by external callers is always the same:
```
POST https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{urlEncodedArn}/invocations?qualifier=DEFAULT
```
After receiving the request, the AgentCore platform layer passes the payload through to the container's corresponding endpoint based on the protocolConfiguration.---
2. HTTP Protocol
Purpose: The most general-purpose protocol, suitable for custom agent applications. The request/response format is entirely defined by the application; AgentCore only performs pass-through.
2.1 Container Contract
| Specification | Requirement |
|---|---|
| Listen Address | 0.0.0.0:8080 |
| Platform | ARM64 container |
| Required Endpoints | POST /invocations, GET /ping |
| Optional Endpoints | GET /ws (WebSocket) |
2.2 Endpoint Details
POST /invocations — Primary Interaction Endpoint
The AgentCore InvokeAgentRuntime API passes the caller's payload as a byte stream as-is to this endpoint. The request body format is defined by the application.
Responses support two modes:
Mode A: JSON (non-streaming)
Content-Type: application/json
{"response": "Query results...", "status": "success"}Suitable for: Simple Q&A, deterministic computations, status queries.
Mode B: SSE (streaming)
Content-Type: text/event-stream
data: {"event": "partial response 1"}
data: {"event": "partial response 2"}
data: {"event": "final response"}Suitable for: Real-time conversations, progressive content generation, long-running operations.
GET /ws — WebSocket (Optional)
Shares port 8080 with /invocations. Establishes a WebSocket connection via standard HTTP Upgrade:
GET /ws HTTP/1.1
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: session-uuidSupports text (JSON / plain text) and binary messages. Suitable for scenarios requiring bidirectional real-time communication.
GET /ping — Health Check
{"status": "Healthy", "time_of_last_update": 1640995200}| Status Value | Meaning |
|---|---|
Healthy | Ready, can accept new requests |
HealthyBusy | Running but has background async tasks (session stays active and will not be reclaimed due to idle timeout) |
2.3 Applicable Scenarios
- Custom agent applications (this project uses this protocol)
- Need for full control over request/response format
- Agents built with Strands Agents, LangGraph, and similar frameworks
- Scenarios requiring WebSocket bidirectional communication
---
3. MCP Protocol
Purpose: Expose an agent's tool and data capabilities in a standardized way. MCP (Model Context Protocol), created by Anthropic, is an open standard for connecting AI applications with external systems (tools, data sources, workflows).
3.1 MCP Protocol Core Concepts
MCP uses a client-server architecture:
MCP Host (AI application, e.g. Claude Code)
|-- MCP Client 1 --> MCP Server A (File System)
|-- MCP Client 2 --> MCP Server B (Database)
+-- MCP Client 3 --> MCP Server C (Sentry)MCP Servers expose three core primitives:
| Primitive | Purpose | Discovery Method |
|---|---|---|
| Tools | Executable functions (file operations, API calls, database queries) | tools/list -> tools/call |
| Resources | Contextual data sources (file contents, database records, API responses) | resources/list -> resources/read |
| Prompts | Interaction templates (system prompts, few-shot examples) | prompts/list -> prompts/get |
Note: The three primitives are defined by the MCP protocol standard. In the AgentCore hosting scenario, Tools is the most commonly used primitive; support for Resources and Prompts depends on the specific MCP Server implementation.
Communication is based on JSON-RPC 2.0, with the transport layer supporting two mechanisms:
- Stdio: Local inter-process communication (standard input/output)
- Streamable HTTP: Remote communication, HTTP POST + optional SSE streaming
3.2 MCP Container Contract in AgentCore
| Specification | Requirement |
|---|---|
| Listen Address | 0.0.0.0:8000 |
| Platform | ARM64 container |
| Primary Endpoint | POST /mcp |
| Health Check | GET /ping |
| Transport | Streamable HTTP |
| Default Mode | Stateless (stateless_http=True) |
Note the port difference: MCP uses 8000, HTTP/AG-UI use 8080, A2A uses 9000.
3.3 Endpoint Details
POST /mcp — MCP RPC Message Handler
AgentCore passes the InvokeAgentRuntime payload as a standard MCP JSON-RPC message directly to this endpoint.
Request Example (Tool Discovery):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}Response Example:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "add_numbers",
"description": "Add two numbers together",
"inputSchema": {
"type": "object",
"properties": {
"a": {"type": "integer"},
"b": {"type": "integer"}
},
"required": ["a", "b"]
}
}
]
}
}Tool Invocation:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "add_numbers",
"arguments": {"a": 3, "b": 5}
}
}Response Content-Type supports application/json or text/event-stream.
3.4 Session Management
- The AgentCore platform automatically adds an
Mcp-Session-Idheader for session isolation - Default stateless mode: The server should not reject platform-generated Session IDs
- Stateful mode (
stateless_http=False): Supports elicitation (multi-turn interaction) and sampling (requesting LLM-generated content)
3.5 Code Example
# my_mcp_server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(host="0.0.0.0", stateless_http=True)
@mcp.tool()
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together"""
return a + b
@mcp.tool()
def greet_user(name: str) -> str:
"""Greet a user by name"""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="streamable-http")Deployment:
agentcore configure -e my_mcp_server.py --protocol MCP
agentcore deployRemote Invocation (Python MCP Client):
from mcp import ClientSession
from mcp.client.streamable_http import streamablehttp_client
encoded_arn = agent_arn.replace(':', '%3A').replace('/', '%2F')
mcp_url = f"https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/{encoded_arn}/invocations?qualifier=DEFAULT"
headers = {
"authorization": f"Bearer {bearer_token}",
"Content-Type": "application/json"
}
async with streamablehttp_client(mcp_url, headers, timeout=120, terminate_on_close=False) as (
read_stream, write_stream, _
):
async with ClientSession(read_stream, write_stream) as session:
await session.initialize()
tools = await session.list_tools()
print(tools)You can also use MCP Inspector (npx @modelcontextprotocol/inspector) for visual testing.
3.6 Relationship with AgentCore Gateway
AgentCore Gateway can register MCP Servers as tools, providing transparent OAuth credential injection for agents:
Agent -> AgentCore Gateway -> MCP Server (hosted on AgentCore Runtime)
|
|-- Inbound: Verify agent identity (JWT)
|-- Tool Routing: MCP tool_name -> target MCP Server
+-- Outbound: Retrieve OAuth token from Token Vault -> inject into request3.7 Applicable Scenarios
- Exposing tool and data capabilities in a standardized way to AI applications
- Need to be invoked by multiple different AI clients (Claude, ChatGPT, VS Code, etc.)
- Providing tool services with OAuth credential injection via AgentCore Gateway
- Already have an MCP Server and want to host it on AgentCore for session isolation and auto-scaling
---
4. A2A Protocol
Purpose: Inter-agent collaboration communication. A2A (Agent-to-Agent), created by Google and donated to the Linux Foundation, is an open standard that enables agents from different frameworks and organizations to collaborate in a peer-to-peer manner.
4.1 A2A Protocol Core Concepts
Agent Opacity: The core design principle of A2A is that agents do not need to share internal memory, private logic, or tool implementations. Collaboration is achieved through message passing, with each agent remaining a black box.
Agent A (Claims Processing) Agent B (Policy Verification)
| |
| Does not know B's internals | Does not know A's internals
| Interacts only via A2A messages | Interacts only via A2A messages
| |
+------ JSON-RPC 2.0 / HTTP ----------+Communication Methods:
- JSON-RPC 2.0 over HTTP(S)
- Supports synchronous request/response, SSE streaming, and asynchronous push notifications
- Data types: text, files, structured JSON
Agent Card: Each A2A agent exposes its metadata at /.well-known/agent-card.json for dynamic discovery:
{
"name": "Calculator Agent",
"description": "A calculator agent for arithmetic operations",
"version": "1.0.0",
"url": "https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/{arn}/invocations/",
"protocolVersion": "0.3.0",
"preferredTransport": "JSONRPC",
"capabilities": {"streaming": true},
"defaultInputModes": ["text"],
"defaultOutputModes": ["text"],
"skills": [
{
"id": "arithmetic",
"name": "Arithmetic",
"description": "Basic arithmetic operations"
}
]
}4.2 A2A Container Contract in AgentCore
| Specification | Requirement |
|---|---|
| Listen Address | 0.0.0.0:9000 |
| Platform | ARM64 container |
| Primary Endpoint | POST / (root path) |
| Agent Card | GET /.well-known/agent-card.json |
| Health Check | GET /ping |
Note: The A2A primary endpoint is the root path/, not/invocations. The port is 9000.
4.3 Endpoint Details
POST / — JSON-RPC 2.0 Message Handler
AgentCore passes the InvokeAgentRuntime payload as a JSON-RPC message directly.
Request Example (Send Message):
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "message/send",
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "what is 101 * 11?"}],
"messageId": "12345678-1234-1234-1234-123456789012"
}
}
}Response Example:
{
"jsonrpc": "2.0",
"id": "req-001",
"result": {
"artifacts": [
{
"artifactId": "unique-artifact-id",
"name": "agent_response",
"parts": [{"kind": "text", "text": "101 * 11 = 1111"}]
}
]
}
}GET /.well-known/agent-card.json — Agent Discovery
Returns the Agent Card JSON, describing the agent's identity, capabilities, skills, and authentication requirements. Callers use this endpoint to learn what the agent can do and how to interact with it.
4.4 Error Handling
A2A errors are returned as JSON-RPC 2.0 error responses, with HTTP status code always 200 (protocol compliance requirement):
| JSON-RPC Code | Meaning | Corresponding HTTP Semantics |
|---|---|---|
| -32501 | Resource not found | 404 |
| -32052 | Request validation failed | 400 |
| -32053 | Rate limit exceeded | 429 |
| -32054 | Resource conflict | 409 |
| -32055 | Runtime client error | 424 |
{
"jsonrpc": "2.0",
"id": "req-001",
"error": {"code": -32052, "message": "Validation error - Invalid request data"}
}4.5 Code Example
# my_a2a_server.py
import os, logging, uvicorn
from strands import Agent
from strands.multiagent.a2a import A2AServer
from strands_tools.calculator import calculator
from fastapi import FastAPI
logging.basicConfig(level=logging.INFO)
runtime_url = os.environ.get('AGENTCORE_RUNTIME_URL', 'http://127.0.0.1:9000/')
strands_agent = Agent(
name="Calculator Agent",
description="A calculator agent that can perform basic arithmetic operations.",
tools=[calculator],
callback_handler=None
)
a2a_server = A2AServer(
agent=strands_agent,
http_url=runtime_url,
serve_at_root=True # AgentCore A2A contract requires the primary endpoint at the / root path
)
app = FastAPI()
@app.get("/ping")
def ping():
return {"status": "healthy"}
app.mount("/", a2a_server.to_fastapi_app())
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=9000)Deployment:
pip install strands-agents[a2a] bedrock-agentcore strands-agents-tools
agentcore configure -e my_a2a_server.py --protocol A2A
agentcore deployFetching the Agent Card Remotely:
ESCAPED_ARN=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$AGENT_ARN', safe=''))")
curl "https://bedrock-agentcore.us-west-2.amazonaws.com/runtimes/${ESCAPED_ARN}/invocations/.well-known/agent-card.json" \
-H "Authorization: Bearer ${BEARER_TOKEN}" \
-H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: $(uuidgen)"4.6 Applicable Scenarios
- Multi-agent collaboration systems (different agents handle their own responsibilities, collaborating via messages)
- Cross-framework agent interoperability (e.g., Strands Agent collaborating with LangGraph Agent)
- Cross-organization agent communication (Agent Card enables dynamic discovery)
- Scenarios requiring agents to remain black boxes without exposing internal implementations
---
5. AG-UI Protocol
Purpose: Standardized communication from agent to frontend UI. AG-UI (Agent-User Interaction Protocol) is led by the CopilotKit team and developed in collaboration with multiple framework teams including LangGraph, CrewAI, AWS Strands, Google ADK, Microsoft Agent Framework, and Pydantic AI. It defines an event stream protocol between agent backends and frontend applications.
5.1 What Problem Does AG-UI Solve
The traditional request/response architecture falls short for agent applications because agents have the following characteristics:
- Long-running: Continuously streaming intermediate results across multi-turn conversations
- Non-deterministic: Agent behavior is unpredictable, requiring dynamic UI control
- Mixed I/O: Simultaneously producing structured data (tool calls, state) and unstructured content (text, voice)
- Human interaction: Requiring pause/approval/edit and other human-in-the-loop capabilities
AG-UI defines 16+ event types that enable the frontend to precisely perceive each step of the agent's behavior.
5.2 AG-UI Container Contract in AgentCore
| Specification | Requirement |
|---|---|
| Listen Address | 0.0.0.0:8080 |
| Platform | ARM64 container |
| Primary Endpoint | POST /invocations (SSE event stream) |
| Optional Endpoints | GET /ws (WebSocket) |
| Health Check | GET /ping |
AG-UI uses the same port (8080) and endpoint path (`/invocations`) as the HTTP protocol. The difference is that the request/response format follows the AG-UI event specification.
5.3 Endpoint Details
POST /invocations — AG-UI Event Stream
Request Format (`RunAgentInput`):
{
"threadId": "thread-123",
"runId": "run-456",
"messages": [{"id": "msg-1", "role": "user", "content": "Hello, agent!"}],
"tools": [],
"context": [],
"state": {},
"forwardedProps": {}
}| Field | Description |
|---|---|
threadId | Conversation thread ID |
runId | Unique ID for this execution |
messages | Message history |
tools | Frontend-registered tools (for frontend tool call scenarios) |
context | Context information |
state | Shared state (supports frontend-backend state synchronization) |
forwardedProps | Pass-through properties |
Response Format (SSE Event Stream):
Content-Type: text/event-stream
data: {"type":"RUN_STARTED","threadId":"thread-123","runId":"run-456"}
data: {"type":"TEXT_MESSAGE_START","messageId":"msg-789","role":"assistant"}
data: {"type":"TEXT_MESSAGE_CONTENT","messageId":"msg-789","delta":"Processing your request"}
data: {"type":"TOOL_CALL_START","toolCallId":"tool-001","toolCallName":"search","parentMessageId":"msg-789"}
data: {"type":"TOOL_CALL_RESULT","messageId":"msg-789","toolCallId":"tool-001","content":"Search completed"}
data: {"type":"TEXT_MESSAGE_END","messageId":"msg-789"}
data: {"type":"RUN_FINISHED","threadId":"thread-123","runId":"run-456"}5.4 AG-UI Event Types
| Event Type | Description |
|---|---|
RUN_STARTED | Agent execution started |
TEXT_MESSAGE_START | Text message stream started |
TEXT_MESSAGE_CONTENT | Incremental text content (delta) |
TEXT_MESSAGE_END | Text message stream ended |
TOOL_CALL_START | Tool invocation started |
TOOL_CALL_RESULT | Tool execution result |
RUN_FINISHED | Agent execution completed |
RUN_ERROR | Execution error |
Errors are returned as SSE events (HTTP status code remains 200):
data: {"type":"RUN_ERROR","code":"AGENT_ERROR","message":"Agent execution failed"}5.5 Code Example
# my_agui_server.py
import uvicorn
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, JSONResponse
# Note: The AG-UI ecosystem is rapidly evolving. The package names and APIs below
# may change with version updates. Please refer to the official documentation at
# ag-ui.com and the latest PyPI versions.
from ag_ui_strands import StrandsAgent
from ag_ui.core import RunAgentInput
from ag_ui.encoder import EventEncoder
from strands import Agent
from strands.models.bedrock import BedrockModel
model = BedrockModel(
model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0",
region_name="us-west-2",
)
strands_agent = Agent(
model=model,
system_prompt="You are a helpful assistant.",
)
agui_agent = StrandsAgent(
agent=strands_agent,
name="my_agent",
description="A helpful assistant",
)
app = FastAPI()
@app.post("/invocations")
async def invocations(input_data: dict, request: Request):
accept_header = request.headers.get("accept")
encoder = EventEncoder(accept=accept_header)
async def event_generator():
run_input = RunAgentInput(**input_data)
async for event in agui_agent.run(run_input):
yield encoder.encode(event)
return StreamingResponse(
event_generator(),
media_type=encoder.get_content_type()
)
@app.get("/ping")
async def ping():
return JSONResponse({"status": "Healthy"})
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)Deployment:
pip install fastapi uvicorn ag-ui-strands
agentcore configure -e my_agui_server.py --protocol AGUI
agentcore deploy5.6 Integration with CopilotKit
AG-UI natively supports CopilotKit. The frontend can use CopilotKit's React components to directly interface with an AG-UI backend:
React Frontend (CopilotKit Components)
<-> AG-UI Event Stream
AgentCore Runtime (AG-UI Protocol)
<-> LLM Invocation
Bedrock / OpenAI / ...Advanced UI capabilities supported by AG-UI include: streaming chat, generative UI, shared state synchronization, thinking steps visualization, human-in-the-loop interruption, sub-agent composition, and more.
5.7 Applicable Scenarios
- Building rich agent UI applications (chat, collaborative editing, approval workflows)
- Frontend needs to precisely perceive each step of the agent's behavior (tool calls, thinking process)
- Using frontend frameworks such as CopilotKit
- Requiring human-in-the-loop interaction (pause, approval, edit)
- Requiring frontend-backend state synchronization
---
6. Comparison of the Four Protocols
| Dimension | HTTP | MCP | A2A | AG-UI |
|---|---|---|---|---|
| Container Port | 8080 | 8000 | 9000 | 8080 |
| Primary Endpoint | /invocations | /mcp | / | /invocations |
| Message Format | Application-defined | JSON-RPC 2.0 | JSON-RPC 2.0 | RunAgentInput + SSE events |
| Communication Mode | JSON / SSE / WebSocket | JSON-RPC (Streamable HTTP) | JSON-RPC / SSE / Async push | SSE event stream / WebSocket |
| Discovery Mechanism | None | tools/list (MCP primitives) | Agent Card (/.well-known/agent-card.json) | None |
| Interaction Target | Human -> Agent | AI App -> Tools/Data | Agent -> Agent | Agent -> Frontend UI |
| Request Body Format | Fully custom | Standard MCP RPC | Standard A2A RPC | AG-UI RunAgentInput |
| Event Types | Custom | MCP standard | A2A standard | 16+ AG-UI events |
| Clients/Frameworks | Any | MCP clients: Claude, ChatGPT, VS Code, Cursor, etc. | Agent frameworks: Strands, LangGraph, CrewAI, etc. | Frontend frameworks: CopilotKit; Agent frameworks: Strands, LangGraph, CrewAI, etc. |
| Protocol Origin | — | Anthropic | Google (Linux Foundation) | CopilotKit + LangGraph + CrewAI |
Complementary Relationship of the Three Protocols
The AG-UI official documentation explicitly defines the complementary positioning of the three protocols:
MCP
Agent <-> Tools/Data
|
+------------+------------+
| | |
A2A Agent AG-UI
Agent <-> Agent | Agent <-> Frontend UI
|
User/System- MCP: The downward connection for agents to access tools and data
- A2A: Horizontal collaboration between agents
- AG-UI: The upward interaction between agents and frontend UI
All three can coexist in the same system: an agent accesses tools via MCP, collaborates with other agents via A2A, and interacts with the user frontend via AG-UI.
---
7. How to Choose a Protocol
What are you building?
|
|-- Custom agent app, need full control over format
| +-- HTTP
|
|-- Tool/data service, to be called by multiple AI clients
| +-- MCP
|
|-- Multi-agent collaboration system
| +-- A2A
|
+-- Agent-driven frontend UI application
+-- AG-UIPractical Considerations:
| Scenario | Recommended Protocol | Rationale |
|---|---|---|
| Strands/LangGraph Agent + custom frontend | HTTP | Most flexible, format freedom |
| Already have an MCP Server, need cloud hosting | MCP | Native compatibility |
| Building a tool service for Claude/ChatGPT | MCP | Standard protocol, widely supported |
| Multiple specialized agents collaborating on complex tasks | A2A | Standard inter-agent communication |
| Building AI collaborative apps with CopilotKit | AG-UI | Native event protocol |
| Simple chatbot | HTTP | Simplest and most straightforward |
Typical choice: If your agent directly faces frontend users, needs fully custom request/response formats, and uses Strands Agents or LangGraph + FastAPI, HTTP is the most natural choice.
---
8. Authentication: Common to All Protocols
Regardless of which protocol is chosen, AgentCore supports the same authentication methods:
OAuth 2.0 Bearer Token:
Authorization: Bearer <jwt-token>
X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: <session-id>Unauthenticated requests return 401:
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://bedrock-agentcore.{region}.amazonaws.com/runtimes/{ESCAPED_ARN}/invocations/.well-known/oauth-protected-resource?qualifier={QUALIFIER}"Clients can discover OAuth endpoints via the resource_metadata URL for automatic token retrieval.
SigV4 Signature Authentication:
- Returns HTTP 403 (not 401)
- Does not include
WWW-Authenticateheader - Invoked via
boto3.client('bedrock-agentcore').invoke_agent_runtime()
Note: OAuth and SigV4 are mutually exclusive; they cannot be used together. After enabling OAuth, you cannot use the boto3 SDK (SigV4) to invoke; you must send HTTPS requests directly with a Bearer Token.
---
This document is based on AWS official documentation (AgentCore Runtime Protocol Contracts), MCP official documentation (modelcontextprotocol.io), A2A official documentation (Google/A2A), and AG-UI official documentation (ag-ui.com). Protocol versions: MCP 2025-06-18, A2A 0.3.0.
#!/usr/bin/env python3
"""AgentCore A2A Server Template.
Deploys as an A2A protocol Runtime on AgentCore.
Port 9000, root endpoint /, Agent Card at /.well-known/agent-card.json.
Usage:
# Local development
python a2a-server-template.py
# Deploy to AgentCore
pip install strands-agents[a2a] strands-agents-tools fastapi uvicorn
agentcore configure -e a2a-server-template.py --protocol A2A
agentcore deploy
"""
import logging
import os
import uvicorn
from fastapi import FastAPI
from strands import Agent
from strands.multiagent.a2a import A2AServer
from strands_tools.calculator import calculator
logging.basicConfig(level=logging.INFO)
runtime_url = os.environ.get("AGENTCORE_RUNTIME_URL", "http://127.0.0.1:9000/")
strands_agent = Agent(
name="Calculator Agent",
description="A calculator agent that can perform basic arithmetic operations.",
tools=[calculator],
callback_handler=None,
)
a2a_server = A2AServer(
agent=strands_agent,
http_url=runtime_url,
serve_at_root=True, # AgentCore A2A contract requires root path /
)
app = FastAPI()
@app.get("/ping")
def ping():
return {"status": "Healthy"}
app.mount("/", a2a_server.to_fastapi_app())
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=9000)
#!/usr/bin/env python3
"""AgentCore AG-UI Server Template.
Deploys as an AG-UI protocol Runtime on AgentCore.
Port 8080, endpoint /invocations, SSE event stream with AG-UI event types.
Note: The AG-UI ecosystem is rapidly evolving. Package names and APIs
may change. Refer to ag-ui.com and PyPI for latest versions.
Usage:
# Local development
pip install fastapi uvicorn ag-ui-strands strands-agents
python agui-server-template.py
# Deploy to AgentCore
agentcore configure -e agui-server-template.py --protocol AGUI
agentcore deploy
"""
import logging
import os
import uvicorn
from ag_ui.core import RunAgentInput
from ag_ui.encoder import EventEncoder
from ag_ui_strands import StrandsAgent
from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import StreamingResponse
from strands import Agent
from strands.models.bedrock import BedrockModel
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
model = BedrockModel(
model_id=os.environ.get("MODEL_ID", "us.anthropic.claude-sonnet-4-5-20250929-v1:0"),
region_name=os.environ.get("AWS_REGION", "us-west-2"),
)
strands_agent = Agent(
model=model,
system_prompt="You are a helpful assistant.",
)
agui_agent = StrandsAgent(
agent=strands_agent,
name="my_agent",
description="A helpful assistant",
)
app = FastAPI()
@app.post("/invocations")
async def invocations(input_data: dict, request: Request):
accept_header = request.headers.get("accept")
encoder = EventEncoder(accept=accept_header)
try:
run_input = RunAgentInput(**input_data)
except Exception as e:
raise HTTPException(status_code=400, detail=f"Invalid input: {e}")
async def event_generator():
try:
async for event in agui_agent.run(run_input):
yield encoder.encode(event)
except Exception:
logger.exception("Error during AG-UI streaming")
raise
return StreamingResponse(
event_generator(),
media_type=encoder.get_content_type(),
)
@app.get("/ping")
async def ping():
return {"status": "Healthy"}
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8080)
# AgentCore Runtime Dockerfile Template (ARM64)
# Multi-stage build for AgentCore Runtime container
#
# Requirements:
# - ARM64 platform (AgentCore requirement)
# - Non-root user (uid=1000)
# - Port 8080
# - /invocations and /ping endpoints
#
# Usage:
# docker build --platform linux/arm64 -t my-agent .
# docker run -p 8080:8080 my-agent
FROM --platform=linux/arm64 ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
WORKDIR /app
COPY pyproject.toml uv.lock ./
# UV_PROJECT_ENVIRONMENT specifies venv location
ENV UV_PROJECT_ENVIRONMENT=/app/.venv
RUN uv venv /app/.venv && \
uv sync --frozen --no-dev --no-cache && \
test -f /app/.venv/bin/uvicorn || (echo "ERROR: uvicorn not found!" && exit 1)
FROM --platform=linux/arm64 ghcr.io/astral-sh/uv:python3.12-bookworm-slim
# Security patches
RUN apt-get update && apt-get upgrade -y && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app/.venv ./.venv
ENV PATH="/app/.venv/bin:$PATH"
ENV PYTHONPATH=/app/.venv/lib/python3.12/site-packages
COPY . ./
RUN useradd -m -u 1000 bedrock_agentcore
USER bedrock_agentcore
EXPOSE 8080
CMD python3 -m uvicorn main:app --host 0.0.0.0 --port 8080
#!/usr/bin/env python3
"""AgentCore MCP Server Template.
Deploys as an MCP protocol Runtime on AgentCore.
Port 8000, endpoint /mcp, Streamable HTTP transport.
Note: FastMCP handles the /mcp endpoint and MCP protocol automatically.
AgentCore's /ping health check is expected on port 8000 — verify that
FastMCP serves it, or add a custom health endpoint if needed.
Usage:
# Local development
python mcp-server-template.py
# Deploy to AgentCore
agentcore configure -e mcp-server-template.py --protocol MCP
agentcore deploy
"""
from mcp.server.fastmcp import FastMCP
mcp = FastMCP(host="0.0.0.0", stateless_http=True)
@mcp.tool()
def add_numbers(a: int, b: int) -> int:
"""Add two numbers together."""
return a + b
@mcp.tool()
def greet_user(name: str) -> str:
"""Greet a user by name."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run(transport="streamable-http")
Related skills
How it compares
AWS deployment pattern for shared agent config—not a local skills.sh installer or a non-AWS container recipe.
FAQ
Who is aws-agentic-ai for?
Developers using Claude Code-family agents or OpenClaw on AWS who need centralized SKILL.md and CLAUDE.md distribution across AgentCore runtimes.
When should I use aws-agentic-ai?
In Build/agent-tooling when designing multi-instance agent hosting; in Build/integrations when connecting S3 Files to your runtime; and in Operate/infra when updating live skills without redeploying containers.
Is aws-agentic-ai safe to install?
It describes cloud infrastructure patterns that imply S3 and runtime access—review IAM boundaries and the Security Audits panel on this page before applying in production accounts.