TypeScript Strands Agent
Generate a TypeScript Strands Agent for building AI agents with tools, and optionally deploy it to Amazon Bedrock AgentCore Runtime. The generator uses tRPC over WebSocket to leverage AgentCore’s bidirectional streaming support for real-time, type-safe communication.
What is Strands?
Section titled “What is Strands?”Strands is a lightweight framework for building AI agents. Key features include:
- Lightweight and customizable: Simple agent loop that gets out of your way
- Production ready: Full observability, tracing, and deployment options for scale
- Model and provider agnostic: Supports many different models from various providers
- Community-driven tools: Powerful set of community-contributed tools
- Multi-agent support: Advanced techniques like agent teams and autonomous agents
- Flexible interaction modes: Conversational, streaming, and non-streaming support
Generate a Strands Agent
Section titled “Generate a Strands Agent”You can generate a TypeScript Strands Agent in two ways:
- Install the Nx Console VSCode Plugin if you haven't already
- Open the Nx Console in VSCode
- Click
Generate (UI)in the "Common Nx Commands" section - Search for
@aws/nx-plugin - ts#strands-agent - Fill in the required parameters
- Click
Generate
pnpm nx g @aws/nx-plugin:ts#strands-agentyarn nx g @aws/nx-plugin:ts#strands-agentnpx nx g @aws/nx-plugin:ts#strands-agentbunx nx g @aws/nx-plugin:ts#strands-agentYou can also perform a dry-run to see what files would be changed
pnpm nx g @aws/nx-plugin:ts#strands-agent --dry-runyarn nx g @aws/nx-plugin:ts#strands-agent --dry-runnpx nx g @aws/nx-plugin:ts#strands-agent --dry-runbunx nx g @aws/nx-plugin:ts#strands-agent --dry-runOptions
Section titled “Options”| Parameter | Type | Default | Description |
|---|---|---|---|
| project Required | string | - | The project to add the Strands Agent to |
| computeType | string | BedrockAgentCoreRuntime | The type of compute to host your Strands Agent. |
| name | string | - | The name of your Strands Agent (default: agent) |
| iacProvider | string | Inherit | The preferred IaC provider. By default this is inherited from your initial selection. |
Generator Output
Section titled “Generator Output”The generator will add the following files to your existing TypeScript project:
Directoryyour-project/
Directorysrc/
Directoryagent/ (or custom name if specified)
- index.ts Entry point for Bedrock AgentCore Runtime
- init.ts tRPC initialization
- router.ts tRPC router with agent procedures
- agent.ts Main agent definition with sample tools
- client.ts Vended client for invoking your agent
- agent-core-trpc-client.ts Client factory for connecting to agents on AgentCore Runtime
- agent-core-mcp-client.ts Client factory for connecting to MCP servers on AgentCore Runtime
- Dockerfile Entry point for hosting your agent (excluded when
computeTypeis set toNone)
- package.json Updated with Strands dependencies
- project.json Updated with agent serve targets
Infrastructure
Section titled “Infrastructure”Since this generator vends infrastructure as code based on your chosen iacProvider, it will create a project in packages/common which includes the relevant CDK constructs or Terraform modules.
The common infrastructure as code project is structured as follows:
Directorypackages/common/constructs
Directorysrc
Directoryapp/ Constructs for infrastructure specific to a project/generator
- …
Directorycore/ Generic constructs which are reused by constructs in
app- …
- index.ts Entry point exporting constructs from
app
- project.json Project build targets and configuration
Directorypackages/common/terraform
Directorysrc
Directoryapp/ Terraform modules for infrastructure specific to a project/generator
- …
Directorycore/ Generic modules which are reused by modules in
app- …
- project.json Project build targets and configuration
For deploying your Strands Agent, the following files are generated:
Directorypackages/common/constructs/src
Directoryapp
Directoryagents
Directory<project-name>
- <project-name>.ts CDK construct for deploying your agent
- Dockerfile Passthrough docker file used by the CDK construct
Directorypackages/common/terraform/src
Directoryapp
Directoryagents
Directory<project-name>
- <project-name>.tf Module for deploying your agent
Directorycore
Directoryagent-core
- runtime.tf Generic module for deploying to Bedrock AgentCore Runtime
Working with Your Strands Agent
Section titled “Working with Your Strands Agent”tRPC over WebSocket
Section titled “tRPC over WebSocket”The TypeScript Strands Agent uses tRPC over WebSocket, leveraging AgentCore’s bidirectional streaming support to enable real-time, type-safe communication between clients and your agent.
Since tRPC supports Query, Mutation and Subscription procedures over WebSocket, you can define any number of procedures. By default, a single subscription procedure named invoke is defined for you in router.ts.
Adding Tools
Section titled “Adding Tools”Tools are functions that the AI agent can call to perform actions. You can add new tools in the agent.ts file:
import { Agent, tool } from '@strands-agents/sdk';import z from 'zod';
const letterCounter = tool({ name: 'letter_counter', description: 'Count occurrences of a specific letter in a word', inputSchema: z.object({ word: z.string().describe('The input word to search in'), letter: z.string().length(1).describe('The specific letter to count'), }), callback: (input) => { const { word, letter } = input; const count = word.toLowerCase().split(letter.toLowerCase()).length - 1; return `The letter '${letter}' appears ${count} time(s) in '${word}'`; },});
// Add tools to your agentexport const agent = new Agent({ systemPrompt: 'You are a helpful assistant with access to various tools.', tools: [letterCounter],});The Strands framework automatically handles:
- Input validation using Zod schemas
- JSON schema generation for tool calling
- Error handling and response formatting
Model Configuration
Section titled “Model Configuration”By default, Strands agents use Claude 4 Sonnet, but you can easily switch between model providers:
import { Agent } from '@strands-agents/sdk';import { BedrockModel } from '@strands-agents/sdk/models/bedrock';import { OpenAIModel } from '@strands-agents/sdk/models/openai';
// Use Bedrockconst bedrockModel = new BedrockModel({ modelId: 'anthropic.claude-sonnet-4-20250514-v1:0',});let agent = new Agent({ model: bedrockModel });let response = await agent.invoke('What can you help me with?');
// Alternatively, use OpenAI by just switching model providerconst openaiModel = new OpenAIModel({ apiKey: process.env.OPENAI_API_KEY, modelId: 'gpt-4o',});agent = new Agent({ model: openaiModel });response = await agent.invoke('What can you help me with?');See the Strands documentation on model providers for more configuration options.
Consuming MCP Servers
Section titled “Consuming MCP Servers”You can add tools from MCP servers to your Strands agent.
For consuming MCP Servers which you have created using the py#mcp-server or ts#mcp-server generators (or others hosted on Bedrock AgentCore Runtime), a client factory is generated for you in agent-core-mcp-client.ts.
You can update your agent initialization in agent.ts to create MCP clients and add tools. The following example shows how to perform this with IAM (SigV4) authentication:
import { Agent } from '@strands-agents/sdk';import { AgentCoreMcpClient } from './agent-core-mcp-client.js';
const mcpClient = AgentCoreMcpClient.withIamAuth({ agentRuntimeArn: process.env.MCP_AGENTCORE_RUNTIME_ARN!, region: process.env.AWS_REGION || 'us-west-2', sessionId: 'my-session-id',});
export const agent = new Agent({ systemPrompt: '...', tools: [mcpClient],});With the IAM authentication example above, we need to configure two things in our infrastructure. Firstly, we need to add the environment variable our agent is consuming for our MCP server’s AgentCore Runtime ARN, and secondly we need to grant our agent permissions to invoke the MCP server. This can be achieved as follows:
import { MyProjectAgent, MyProjectMcpServer } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const mcpServer = new MyProjectMcpServer(this, 'MyProjectMcpServer');
const agent = new MyProjectAgent(this, 'MyProjectAgent', { environmentVariables: { MCP_AGENTCORE_RUNTIME_ARN: mcpServer.agentCoreRuntime.agentRuntimeArn, }, });
mcpServer.agentCoreRuntime.grantInvoke(agent.agentCoreRuntime); }}# MCP Servermodule "my_project_mcp_server" { source = "../../common/terraform/src/app/mcp-servers/my-project-mcp-server"}
# Agentmodule "my_project_agent" { source = "../../common/terraform/src/app/agents/my-project-agent"
env = { MCP_AGENTCORE_RUNTIME_ARN = module.my_project_mcp_server.agent_core_runtime_arn }
additional_iam_policy_statements = [ { Effect = "Allow" Action = [ "bedrock-agentcore:InvokeAgentRuntime" ] Resource = [ module.my_project_mcp_server.agent_core_runtime_arn, "${module.my_project_mcp_server.agent_core_runtime_arn}/*" ] } ]}For a more in-depth guide to writing Strands agents, refer to the Strands documentation.
Running Your Strands Agent
Section titled “Running Your Strands Agent”Local Development
Section titled “Local Development”The generator configures a target named <your-agent-name>-serve, which starts your Strands Agent locally for development and testing.
pnpm nx run your-project:agent-serveyarn nx run your-project:agent-servenpx nx run your-project:agent-servebunx nx run your-project:agent-serveThis command uses tsx --watch to automatically restart the server when files change. The agent will be available at http://localhost:8081 (or the assigned port if you have multiple agents).
Deploying Your Strands Agent to Bedrock AgentCore Runtime
Section titled “Deploying Your Strands Agent to Bedrock AgentCore Runtime”Infrastructure as Code
Section titled “Infrastructure as Code”If you selected BedrockAgentCoreRuntime for computeType, the relevant CDK or Terraform infrastructure is generated which you can use to deploy your Strands Agent to Amazon Bedrock AgentCore Runtime.
A CDK construct is generated for your agent, named based on the name you chose when running the generator, or <ProjectName>Agent by default.
You can use this CDK construct in a CDK application:
import { MyProjectAgent } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { // Add the agent to your stack const agent = new MyProjectAgent(this, 'MyProjectAgent');
// Grant permissions to invoke the relevant models in bedrock agent.agentCoreRuntime.addToRolePolicy( new PolicyStatement({ actions: [ 'bedrock:InvokeModel', 'bedrock:InvokeModelWithResponseStream', ], // You can scope the below down to the specific models you use resources: [ 'arn:aws:bedrock:*:*:foundation-model/*', 'arn:aws:bedrock:*:*:inference-profile/*', ], }), ); }}A Terraform module is generated for you, named based on the name you chose when running the generator, or <ProjectName>-agent by default.
You can use this terraform module in a Terraform project:
# Agentmodule "my_project_agent" { # Relative path to the generated module in the common/terraform project source = "../../common/terraform/src/app/agents/my-project-agent"
# Grant permissions to invoke the relevant models in bedrock additional_iam_policy_statements = [ { Effect = "Allow" Action = [ "bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream" ] # You can scope the below down to the specific models you use Resource = [ "arn:aws:bedrock:*:*:foundation-model/*", "arn:aws:bedrock:*:*:inference-profile/*" ] } ]}Authentication
Section titled “Authentication”By default, your Strands Agent will be secured using IAM authentication, simply deploy it without any arguments:
import { MyProjectAgent } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { new MyProjectAgent(this, 'MyProjectAgent'); }}You can grant access to invoke your agent on Bedrock AgentCore Runtime using the grantInvoke method, for example:
import { MyProjectAgent } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const agent = new MyProjectAgent(this, 'MyProjectAgent'); const lambdaFunction = new Function(this, ...);
agent.agentCoreRuntime.grantInvoke(lambdaFunction); }}# Agentmodule "my_project_agent" { # Relative path to the generated module in the common/terraform project source = "../../common/terraform/src/app/agents/my-project-agent"}To grant access to invoke your agent, you will need to add a policy such as the following, referencing the module.my_project_agent.agent_core_runtime_arn output:
{ Effect = "Allow" Action = [ "bedrock-agentcore:InvokeAgentRuntime" ] Resource = [ module.my_project_agent.agent_core_runtime_arn, "${module.my_project_agent.agent_core_runtime_arn}/*" ]}Cognito JWT Authentication
Section titled “Cognito JWT Authentication”The below demonstrates how to configure Cognito authentication for your agent.
To configure JWT authentication using Cognito, use the RuntimeAuthorizerConfiguration.usingCognito() factory method:
import { MyProjectAgent } from ':my-scope/common-constructs';import { RuntimeAuthorizerConfiguration } from '@aws-cdk/aws-bedrock-agentcore-alpha';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const userPool = new UserPool(this, 'UserPool'); const client = userPool.addClient('Client', { authFlows: { userPassword: true, }, });
new MyProjectAgent(this, 'MyProjectAgent', { authorizerConfiguration: RuntimeAuthorizerConfiguration.usingCognito( userPool, [client], ), }); }}Alternatively, for custom JWT authentication with your own OIDC provider, use RuntimeAuthorizerConfiguration.usingJWT():
import { MyProjectAgent } from ':my-scope/common-constructs';import { RuntimeAuthorizerConfiguration } from '@aws-cdk/aws-bedrock-agentcore-alpha';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { new MyProjectAgent(this, 'MyProjectAgent', { authorizerConfiguration: RuntimeAuthorizerConfiguration.usingJWT( 'https://example.com/.well-known/openid-configuration', ['client1', 'client2'], // Allowed Client IDs (optional) ['audience1'], // Allowed Audiences (optional) ), }); }}To configure JWT authentication, you can edit your agent module to configure the authorizer_configuration variable as follows:
data "aws_region" "current" {}
locals { aws_region = data.aws_region.current.id
# Replace with your user pool and client ids or expose as variables user_pool_id = "xxx" user_pool_client_ids = ["yyy"]}
module "agent_core_runtime" { source = "../../../core/agent-core" agent_runtime_name = "MyProjectAgent" docker_image_tag = "my-scope-my-project-agent:latest" server_protocol = "HTTP" authorizer_configuration = { custom_jwt_authorizer = { discovery_url = "https://cognito-idp.${local.aws_region}.amazonaws.com/${local.user_pool_id}/.well-known/openid-configuration" allowed_clients = local.user_pool_client_ids } } env = var.env additional_iam_policy_statements = var.additional_iam_policy_statements tags = var.tags}Bundle Target
Section titled “Bundle Target”The generator automatically configures a bundle target which uses Rolldown to create a deployment package:
pnpm nx run <project-name>:bundleyarn nx run <project-name>:bundlenpx nx run <project-name>:bundlebunx nx run <project-name>:bundleRolldown configuration can be found in rolldown.config.ts, with an entry per bundle to generate. Rolldown manages creating multiple bundles in parallel if defined.
The bundle target uses index.ts as the entrypoint for the WebSocket server to host on Bedrock AgentCore Runtime.
Docker Target
Section titled “Docker Target”The generator configures a <your-agent-name>-docker target which runs the bundled WebSocket server on port 8080 as per the AgentCore runtime contract.
A docker target is also generated which runs the docker build for all agents if you have multiple defined.
Observability
Section titled “Observability”Your agent is automatically configured with observability using the AWS Distro for Open Telemetry (ADOT), by configuring auto-instrumentation in your Dockerfile.
You can find traces in the CloudWatch AWS Console, by selecting “GenAI Observability” in the menu. Note that for traces to be populated you will need to enable Transaction Search.
For more details, refer to the AgentCore documentation on observability.
Invoking your Strands Agent
Section titled “Invoking your Strands Agent”Agent communication is transmitted via tRPC over WebSocket. As such, it’s recommended to use the generated type-safe client factory in client.ts.
Invoke the Local Server
Section titled “Invoke the Local Server”You can invoke a locally running agent using the .local factory method from the client factory.
You can, for example create a file named scripts/test.ts in your workspace which imports the client:
import { AgentClient } from '../packages/<project>/src/agent/client.js';
const client = AgentClient.local({ url: 'http://localhost:8081/ws' });
client.invoke.subscribe({ message: 'what is 1 plus 1?' }, { onData: console.log });Invoke the Deployed Agent
Section titled “Invoke the Deployed Agent”To invoke your Agent deployed to Bedrock AgentCore Runtime, you can send a POST request to the Bedrock AgentCore Runtime dataplane endpoint with your URL-encoded runtime ARN.
You can obtain the runtime ARN from your infrastructure as follows:
import { CfnOutput } from 'aws-cdk-lib';import { MyProjectAgent } from ':my-scope/common-constructs';
export class ExampleStack extends Stack { constructor(scope: Construct, id: string) { const agent = new MyProjectAgent(this, 'MyProjectAgent');
new CfnOutput(this, 'AgentArn', { value: agent.agentCoreRuntime.agentRuntimeArn, }); }}# Agentmodule "my_project_agent" { # Relative path to the generated module in the common/terraform project source = "../../common/terraform/src/app/agents/my-project-agent"}
output "agent_arn" { value = module.my_project_agent.agent_core_runtime_arn}The ARN will have the following format: arn:aws:bedrock-agentcore:<region>:<account>:runtime/<agent-runtime-id>.
You can then URL-encode the ARN by replacing : with %3A and / with %2F.
The Bedrock AgentCore Runtime dataplane URL for invoking the agent is as follows:
https://bedrock-agentcore.<region>.amazonaws.com/runtimes/<url-encoded-arn>/invocationsThe exact way to invoke this URL depends upon the authentication method used.
NodeJS
Section titled “NodeJS”The generated client.ts file includes a type-safe client factory which can be used to invoke your deployed agent.
IAM Authentication
Section titled “IAM Authentication”You can invoke your deployed agent by passing its ARN to the withIamAuth factory method:
import { AgentClient } from './agent/client.js';
const client = AgentClient.withIamAuth({ agentRuntimeArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my-agent',});
client.invoke.subscribe({ message: 'what is 1 plus 1?' }, { onData: (message) => console.log(message), onError: (error) => console.error(error), onComplete: () => console.log('Done'),});JWT / Cognito Authentication
Section titled “JWT / Cognito Authentication”Use the withJwtAuth factory method to authenticate with the JWT / Cognito access token.
const client = AgentClient.withJwtAuth({ agentRuntimeArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my-agent', accessTokenProvider: async () => `<access-token>`,});
client.invoke.subscribe({ message: 'what is 1 plus 1?' }, { onData: console.log,});The accessTokenProvider must return the token used to authenticate the request. You can, for example, obtain a token within this method to ensure that fresh credentials are reused when tRPC restarts a WebSocket connection. The below demonstrates using the AWS SDK to obtain the token from Cognito:
import { CognitoIdentityProvider } from "@aws-sdk/client-cognito-identity-provider";
const cognito = new CognitoIdentityProvider();
const jwtClient = AgentClient.withJwtAuth({ agentRuntimeArn: 'arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my-agent', accessTokenProvider: async () => { const response = await cognito.adminInitiateAuth({ UserPoolId: '<user-pool-id>', ClientId: '<user-pool-client-id>', AuthFlow: 'ADMIN_NO_SRP_AUTH', AuthParameters: { USERNAME: '<username>', PASSWORD: '<password>', }, }); return response.AuthenticationResult!.AccessToken!; },});Browser
Section titled “Browser”WebSockets in the browser do not support specifying headers (other than Sec-WebSocket-Protocol), and therefore the generated client factory in client.ts cannot be used in a browser (this will actually result in a compilation error as the WebSocket constructor does not accept headers as it does in NodeJS).
JWT / Cognito Authentication
Section titled “JWT / Cognito Authentication”IAM Authentication
Section titled “IAM Authentication”To invoke your agent from a browser, you need to create a presigned WebSocket URL using AWS SigV4.
The below example shows an end-to-end flow of obtaining credentials, creating a presigned URL, and invoking the agent:
import { createTRPCClient, createWSClient, wsLink } from '@trpc/client';import { AwsClient } from 'aws4fetch';import { CognitoIdentityClient } from '@aws-sdk/client-cognito-identity';import { fromCognitoIdentityPool } from '@aws-sdk/credential-provider-cognito-identity';import type { AppRouter } from './your-agent/router';
// Build a presigned WebSocket URLasync function buildSignedUrl( agentRuntimeArn: string, idToken: string, region: string = 'us-west-2'): Promise<string> { // Get credentials from a Cognito Identity Pool (or other source) const credentials = fromCognitoIdentityPool({ client: new CognitoIdentityClient({ region }), identityPoolId: 'us-west-2:xxxxx', logins: { [`cognito-idp.${region}.amazonaws.com/us-west-2_xxxxx`]: idToken, }, });
const cognitoIdentity = new CognitoIdentityClient({ credentials }); const credential = await cognitoIdentity.config.credentials();
// Create AWS SigV4 client const awsClient = new AwsClient({ ...credential, service: 'bedrock-agentcore', });
// Build WebSocket URL from ARN const wsUrl = `wss://bedrock-agentcore.${region}.amazonaws.com/runtimes/${agentRuntimeArn.replace(/:/g, '%3A').replace(/\//g, '%2F')}/ws`;
// Create presigned URL const signedRequest = await awsClient.sign(wsUrl, { method: 'GET', aws: { signQuery: true }, });
return signedRequest.url;}
// Create tRPC client with presigned WebSocket URLconst agentRuntimeArn = 'arn:aws:bedrock-agentcore:us-west-2:123456789012:runtime/my-agent';const idToken = '<your-id-token>';
const wsClient = createWSClient({ url: async () => buildSignedUrl(agentRuntimeArn, idToken),});
const trpcClient = createTRPCClient<AppRouter>({ links: [wsLink({ client: wsClient })],});
// Invoke the agenttrpcClient.invoke.subscribe({ message: 'what is 1 plus 1?' }, { onData: (message) => console.log(message),});