@aws/agentcore-cdk
L3 CDK constructs for Amazon Bedrock AgentCore. Deploy agent infrastructure with strongly-typed, declarative specifications.
Installation
npm install @aws/agentcore-cdk
Note: aws-cdk-lib and constructs are peer dependencies. If you don't already have them:
npm install @aws/agentcore-cdk aws-cdk-lib constructs
Quick Start
An AgentCore project is a directory with an agentcore/agentcore.json (written by agentcore project create). The CDK
app the AgentCore CLI vends into agentcore/cdk/ is the smallest complete use of this library — three calls in
bin/cdk.ts and one construct in lib/cdk-stack.ts:
// bin/cdk.ts
#!/usr/bin/env node
import { App } from 'aws-cdk-lib';
import { readAgentCoreProject, resolveTargetStacks, transformAgentCoreJson } from '@aws/agentcore-cdk';
import { AgentCoreStack } from '../lib/cdk-stack';
try {
// The AgentCore CLI runs this app from agentcore/cdk/; readAgentCoreProject walks up to agentcore/.
const project = readAgentCoreProject();
const app = new App();
for (const stack of resolveTargetStacks({
projectName: project.projectName,
targets: project.targets,
deployedState: project.deployedState,
})) {
new AgentCoreStack(app, stack.stackName, {
env: stack.env,
tags: stack.tags,
description: stack.description,
application: transformAgentCoreJson(project.agentCoreJson, {
projectRoot: project.projectRoot,
credentials: stack.credentials,
target: stack.target,
}),
});
}
app.synth();
} catch (error) {
console.error('AgentCore CDK synthesis failed:', error instanceof Error ? error.message : error);
process.exit(1);
}
// lib/cdk-stack.ts
import { AgentCoreApplication, type AgentCoreApplicationProps } from '@aws/agentcore-cdk';
import { Stack, type StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';
export interface AgentCoreStackProps extends StackProps {
/** Props for the AgentCore application, produced by transformAgentCoreJson from agentcore/agentcore.json. */
application: AgentCoreApplicationProps;
}
/**
* The stack the AgentCore CLI deploys. Everything declared in agentcore/agentcore.json is
* created by the AgentCoreApplication construct. Add your own resources below it and wire
* them to your runtimes and harnesses through the application's accessors.
*/
export class AgentCoreStack extends Stack {
public readonly application: AgentCoreApplication;
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
super(scope, id, props);
this.application = new AgentCoreApplication(this, 'Application', props.application);
// Example: give a runtime a DynamoDB table.
//
// import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
// const orders = new dynamodb.Table(this, 'Orders', {
// partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
// });
// const checkout = this.application.runtime('checkout'); // or .harness('support')
// checkout.grantReadWrite(orders);
// checkout.addEnvironmentVariable('ORDERS_TABLE', orders.tableName);
}
}
The three entry points:
| Function | What it does |
|---|---|
readAgentCoreProject(startDir = process.cwd()) | Walks up to the nearest agentcore/agentcore.json and reads the project files once: the parsed agentcore.json, the validated project name, aws-targets.json ([] when absent) and agentcore/.cli/deployed-state.json (undefined when absent, a malformed file throws). The only place the app touches the working directory. |
resolveTargetStacks({ projectName, targets, deployedState }) | One TargetStack per deployment target, AgentCore-<project>-<target>, pinned to the target's account/region, tagged agentcore:project-name and agentcore:target-name, with that target's credential providers, or one environment-agnostic AgentCore-<project> stack (project tag only) when there are no targets yet. |
transformAgentCoreJson(agentCoreJson, { projectRoot, credentials, target }) | Validates agentcore.json, reads each harness's harness.yaml and system-prompt.md and every non-S3 knowledge-base connector file under projectRoot, and returns the AgentCoreApplicationProps for one AgentCoreApplication. target is the stack's deployment target, whose name becomes AgentCoreApplicationProps.targetName. Synchronous, deterministic, no network, no process.env. |
Every relative path in the spec resolves against projectRoot, so synthesis never depends on the working directory.
Every physical AWS resource name is <project><sep><target><sep><name>: the project name, the deployment target name
and the resource's spec name, joined by the separator the service accepts (_ for AgentCore resources other than
gateways and for knowledge bases, - for gateways and Lambda functions, / for ECR repositories, which are also
lowercased). Project names are letters and digits and target names are letters, digits and hyphens. With the _
separator each - in the target becomes _, because those services only allow letters, digits and underscores, so
runtime checkout deployed to target prod-us is Shop_prod_us_checkout; the - and / separators keep the target
as is. The target reaches the constructs through transformAgentCoreJson's target option, which sets
AgentCoreApplicationProps.targetName, and the vended bin/cdk.ts passes target: stack.target, so runtime checkout
of project Shop deployed to target prodEu is Shop_prodEu_checkout. Without a target (the project build stack, or
AgentCoreApplication used without targetName) the name is <project><sep><name>. A composed name longer than the
service's limit fails at synth, naming the resource, its length and the limit.
Constructs
AgentCoreApplication
Main L3 construct for deploying agent environments. It takes the project spec (agentcore.json), the harness
configurations, the deployed credentials, an optional targetName (the deployment target, the middle segment of every
physical resource name) and a required projectRoot (the directory that contains agentcore/; every relative path in
the spec resolves against it, so synthesis never depends on the working directory).
The application creates everything agentcore.json declares, so one construct owns the whole spec. Gateways
(Gateway<name>), the compute behind their targets (LambdaToolTarget<name>, RuntimeToolTarget<name>) and standalone
tool runtimes (ToolRuntime<name>) are direct children of the application. Payment managers and connectors are created
under the Payments child at stack scope, with the construct ids the AgentCore CLI's original template used
(Payment<manager> and Payment<manager><connector>, underscores removed), so a stack that template deployed keeps its
resources. Those ids must not change.
Once every resource exists the application binds each one to the workloads that use it (see
Binding AgentCore resources): every memory and gateway to every runtime, payment
managers to Python HTTP runtimes, each harness's referenced memory and every gateway to that harness, and each
connections[] entry and tool runtime binding to the workload that names it.
RuntimeEnvironment
One entry of runtimes[]: the AgentCoreRuntime, its build resources, its endpoints and its outputs.
Extending the application
Get resources
Use a getter when the resource must exist. A typo or missing resource fails at synth and lists the available names:
const checkout = this.application.runtime('checkout');
const support = this.application.harness('support');
const tools = this.application.gateway('tools');
const history = this.application.memory('history');
Other getters are knowledgeBase(name), evaluator(name), onlineEvalConfig(name), policyEngine(name),
configBundle(name), capacityProvider(name), toolRuntime(name), paymentManager(name) and
paymentConnector(manager, connector). Use the corresponding typed map, such as memories.get(name), when a resource
is optional or when iterating. runtimeEnvironments, harnessRoles and toolLambdaFunctions are map-only. A legacy
role-only harness is available through harnessRoles; harness(name) returns only a fully synthesized harness.
Grant permissions
The constructs that own an execution role (AgentCoreRuntime, AgentCoreHarness, AgentCoreHarnessRole and
AgentCoreGateway) implement iam.IGrantable, so any AWS L2 grant accepts them. Runtimes and harnesses also expose
grantRead / grantWrite / grantReadWrite for DynamoDB tables, S3 buckets and Secrets Manager secrets (each
delegates to the resource's own grant method, so the IAM actions stay owned by aws-cdk-lib),
grant(actions, resources), addToPolicy / addToRolePolicy, and addEnvironmentVariable:
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
const orders = new dynamodb.Table(this, 'Orders', {
partitionKey: { name: 'pk', type: dynamodb.AttributeType.STRING },
});
const checkout = this.application.runtime('checkout');
checkout.grantReadWrite(orders); // updates the runtime's execution role
checkout.addEnvironmentVariable('ORDERS_TABLE', orders.tableName);
orders.grantReadData(this.application.harness('support')); // any AWS L2 grant works too
const reports = new s3.Bucket(this, 'Reports');
checkout.grantWrite(reports, 'checkout/*'); // s3:PutObject + s3:DeleteObject* on checkout/* only
const apiKey = new secretsmanager.Secret(this, 'PartnerApiKey');
checkout.grantRead(apiKey); // secrets are read-only for a workload; grantWrite(secret) throws
checkout.addEnvironmentVariable('PARTNER_API_KEY_ARN', apiKey.secretArn);
AgentCore resources also provide grants for their own data-plane operations: runtimes have grantInvoke, grantExec
and grantInvokeAll; harnesses and gateways have grantInvoke; memories have grant and grantWithAccess; payment
managers have grantProcessPayment. Use these when the caller already knows how to find the resource:
const support = this.application.harness('support');
this.application.runtime('search').grantInvoke(support);
this.application.memory('history').grantWithAccess(support, 'read');
this.application.gateway('tools').grantInvoke(support);
this.application.paymentManager('billing').grantProcessPayment(support);
Imported roles. A runtime, harness or gateway configured with an executionRoleArn uses a role CDK cannot modify.
Every grant path — the conveniences, grant, addToPolicy, and any L2 grant made through grantPrincipal — emits a
synth-time warning listing the actions that were not attached; the imported role must already carry them.
Environment variable limits. Both resources accept at most 50 variables; keys must match ^[a-zA-Z_][a-zA-Z0-9_]*$
and be 1–100 characters; values may be at most 2048 characters. addEnvironmentVariable validates at call time and
names the construct, the key and the limit; setting a key to the value it already has is a no-op, setting it to a
different value is an error.
Binding AgentCore resources
AgentCoreMemory, AgentCoreGateway, AgentCorePaymentManager and AgentCoreRuntime implement IBindable.
bind(workload) grants the workload's execution role the resource's data-plane access through the resource's own grant
method and sets the variables the workload's code reads to find it, all under the resource's discoveryPrefix:
MEMORY_<NAME> for a memory, AGENTCORE_<TYPE>_<NAME> for a gateway, payment manager or runtime, and <TYPE>_<ID> for
a resource imported by ARN, with the name uppercased and every other character replaced by _. The memory and imported
names, and the credential names in the table below, match what the v0 CLI deployed. Runtimes and harnesses are the
workloads (IAgentCoreWorkload). Use grant* when only IAM permission is needed; use bind when the workload also
needs discovery environment variables.
The application binds the relationships declared in agentcore.json. A stack extending the application can bind
additional relationships or resources imported by ARN:
import { AgentCoreMemory } from '@aws/agentcore-cdk';
const checkout = this.application.runtime('checkout');
const shared = AgentCoreMemory.fromMemoryArn(
this,
'shared',
'arn:aws:bedrock-agentcore:us-west-2:123456789012:memory/x'
);
shared.bind(checkout, { access: 'read' }); // MEMORY_SHARED_ID and _ARN, read actions on the ARN
const support = this.application.harness('support');
this.application.toolRuntime('search').bind(support, { envVarName: 'SEARCH_TOOL_ARN', exec: true });
this.application.paymentManager('billing').bind(support);
| Resource | Prefix | Variables |
|---|---|---|
| Memory | MEMORY_<spec name>, or MEMORY_<import id> | ID, ARN |
| Gateway | AGENTCORE_GATEWAY_<spec name>, or GATEWAY_<import id> | URL, ARN, AUTH_TYPE (AWS_IAM, CUSTOM_JWT, NONE), CREDENTIAL_PROVIDER for CUSTOM_JWT |
| Payment manager | AGENTCORE_PAYMENT_<spec name> | MANAGER_ARN, PROCESS_PAYMENT_ROLE_ARN, CONNECTOR_ID (first connector), AUTO_PAYMENT, TOOL_ALLOWLIST, NETWORK_PREFERENCES, AUTH_MODE |
| Runtime (as a tool) | AGENTCORE_RUNTIME_<runtime name>, or RUNTIME_<import id> | ARN, or the binding's envVarName verbatim |
| Browser, code interpreter (connections) | BROWSER_<connection id>, CODE_INTERPRETER_<connection id> | ID when an ARN is given, nothing for the AWS default |
| Credential | CREDENTIAL_<credential name>, . and - replaced by _ | NAME, the provisioned provider name (from the recorded ARN) |
For a project Gateway with protocolType: "MCP", URL is the complete MCP endpoint returned by the service. For
protocolType: "None", URL is the bare Gateway origin: append /mcp to use its MCP interface, or append the route
for the HTTP target being called. Imported Gateways remain MCP-oriented because an ARN does not carry protocol metadata,
so their derived URL ends in /mcp.
The import id of a connections[] entry is connectionTokenFor(connection): the connection id, or
<type>-<resource id> when there is none, uppercased with every other character replaced by _, the same token the
AgentCore CLI bakes into generated code. Binding the same resource to a workload twice is a no-op. Two resources that
share a prefix and set a variable to different values fail at synth naming the workload and the variable.
Container builds in a VPC
When a Container-build agent (or harness) sets networkMode: VPC, the build infrastructure — the orchestrator Lambda
and the shared CodeBuild project — is placed in the same VPC as the runtime, using networkConfig.subnets and
networkConfig.securityGroups. CodeBuild additionally requires networkConfig.vpcId (its API cannot infer the VPC from
subnets); it is required only for Container builds in VPC mode.
All Container agents and harnesses in a single project must build in the same VPC — the CodeBuild project is shared per stack and synthesis fails fast on a mismatch. Container builds in VPC mode allow at most 5 security groups (a CodeBuild limit), vs 16 for the runtime.
Network prerequisites. Private subnets must be able to reach the services the build uses, or the build will hang until timeout:
- The build Lambda calls CodeBuild (
StartBuild/BatchGetBuilds) and must reach the CloudFormation response endpoint (S3). - CodeBuild must reach ECR (api + dkr), S3 (source asset), CloudWatch Logs, STS, and any registry the Dockerfile pulls base images from.
Provide a NAT gateway or VPC endpoints for: ECR (api, dkr), S3 (gateway), CloudWatch Logs, CodeBuild, and STS — plus egress for public base-image pulls.
IAM authentication for Gateway targets
MCP server and OpenAPI targets can authenticate with the Gateway's execution role by setting outboundAuth.type to
GATEWAY_IAM_ROLE. Set service to the target's SigV4 service name, such as bedrock-agentcore for an AgentCore
Runtime or execute-api for an API Gateway endpoint. region is optional; when omitted, signing uses the Gateway's
region. No named OAuth or API key credential is needed.
{
"name": "runtime-tools",
"targetType": "mcpServer",
"endpoint": "<IAM-protected MCP endpoint URL>",
"outboundAuth": {
"type": "GATEWAY_IAM_ROLE",
"service": "bedrock-agentcore",
"region": "us-west-2"
}
}
OpenAPI targets use the same outboundAuth configuration alongside their schemaSource. For an external target, grant
the required target-service permissions to the Gateway's execution role. For example, an AgentCore Runtime requires
bedrock-agentcore:InvokeAgentRuntime; an API Gateway endpoint requires execute-api:Invoke. The service and
region fields are valid only with GATEWAY_IAM_ROLE.
Standalone Usage
These constructs can be used independently without the CLI. Build the props from a project directory:
import { AgentCoreApplication, readAgentCoreProject, transformAgentCoreJson } from '@aws/agentcore-cdk';
const project = readAgentCoreProject('/path/to/my-project');
new AgentCoreApplication(
this,
'App',
transformAgentCoreJson(project.agentCoreJson, { projectRoot: project.projectRoot })
);
or pass a spec directly with the required project root that the spec's relative paths resolve against:
import { AgentCoreApplication } from '@aws/agentcore-cdk';
new AgentCoreApplication(this, 'App', {
spec: {
name: 'myProject',
version: 2,
runtimes: [
{
name: 'myAgent',
build: 'CodeZip',
entrypoint: 'main.py',
codeLocation: 'app/myAgent/',
runtimeVersion: 'PYTHON_3_12',
},
],
},
projectRoot: '/path/to/my-project',
});
Related Package
@aws/agentcore- CLI tool that uses these constructs via templated CDK projects
Security
See CONTRIBUTING for more information.
License
This project is licensed under the Apache-2.0 License.