Agentic AI Dungeon Game
Module 3: Story Agent implementation
Section titled “Module 3: Story Agent implementation”The Story Agent is a Strands agent which, given a Game
and a list of Action
s for context, will progress a story. We will configure the agent to interact with our Inventory MCP Server to manage the player’s available items.
Agent Implementation
Section titled “Agent Implementation”Let’s implement our agent. Update the following files in packages/story/dungeon_adventure_story/agent
:
import osfrom bedrock_agentcore.runtime import BedrockAgentCoreApp
from .agent import get_agent
app = BedrockAgentCoreApp()
PORT = int(os.environ.get("PORT", "8080"))
@app.entrypointasync def invoke(payload, context): """Handler for agent invocation""" player_name = payload.get("playerName") genre = payload.get("genre") actions = payload.get("actions")
messages = [{"role": "user", "content": [{"text": "Continue or create a new story..."}]}] for action in actions: messages.append({"role": action["role"], "content": [{"text": action["content"]}]})
with get_agent(player_name, genre, session_id=context.session_id) as agent: stream = agent.stream_async(messages) async for event in stream: print(event) yield (event)
if __name__ == "__main__": app.run(port=PORT)
import osfrom bedrock_agentcore.runtime import BedrockAgentCoreApp
from .agent import get_agent
app = BedrockAgentCoreApp()
PORT = int(os.environ.get("PORT", "8080"))
@app.entrypointasync def invoke(payload, context): """Handler for agent invocation""" prompt = payload.get( "prompt", "No prompt found in input, please guide the user " "to create a json payload with prompt key" ) player_name = payload.get("playerName") genre = payload.get("genre") actions = payload.get("actions")
with get_agent(session_id=context.session_id) as agent: stream = agent.stream_async(prompt) messages = [{"role": "user", "content": [{"text": "Continue or create a new story..."}]}] for action in actions: messages.append({"role": action["role"], "content": [{"text": action["content"]}]})
with get_agent(player_name, genre, session_id=context.session_id) as agent: stream = agent.stream_async(messages) async for event in stream: print(event) yield (event)
if __name__ == "__main__": app.run() app.run(port=PORT)
import osfrom contextlib import contextmanager
import boto3from strands import Agent
from .agentcore_mcp_client import AgentCoreMCPClient
# Obtain the region and credentialsregion = os.environ["AWS_REGION"]boto_session = boto3.Session(region_name=region)credentials = boto_session.get_credentials()
@contextmanagerdef get_agent(player_name: str, genre: str, session_id: str): mcp_client = AgentCoreMCPClient.with_iam_auth( agent_runtime_arn=os.environ["INVENTORY_MCP_ARN"], credentials=credentials, region=region, session_id=session_id, ) with mcp_client: yield Agent( system_prompt=f"""You are running a text adventure game in the genre <genre>{genre}</genre> for player <player>{player_name}</player>.Construct a scenario and give the player decisions to make.Use the tools to manage the player's inventory as items are obtained or lost.When adding, removing or updating items in the inventory, always list items to check the current state,and be careful to match item names exactly. Item names in the inventory must be Title Case.Ensure you specify a suitable emoji when adding items if available.When starting a game, populate the inventory with a few initial items. Items should be a key part of the narrative.Keep responses under 100 words.""", tools=[*mcp_client.list_tools_sync()], )
import osfrom contextlib import contextmanager
from strands import Agent, toolfrom strands_tools import current_timeimport boto3from strands import Agent
from .agentcore_mcp_client import AgentCoreMCPClient
# Define a custom tool@tooldef add(a: int, b: int) -> int: return a + b# Obtain the region and credentialsregion = os.environ["AWS_REGION"]boto_session = boto3.Session(region_name=region)credentials = boto_session.get_credentials()
@contextmanagerdef get_agent(session_id: str): yield Agent( system_prompt="""You are an addition wizard.Use the 'add' tool for addition tasks.Refer to tools as your 'spellbook'.""", tools=[add, current_time],def get_agent(player_name: str, genre: str, session_id: str): mcp_client = AgentCoreMCPClient.with_iam_auth( agent_runtime_arn=os.environ["INVENTORY_MCP_ARN"], credentials=credentials, region=region, session_id=session_id, ) with mcp_client: yield Agent( system_prompt=f"""You are running a text adventure game in the genre <genre>{genre}</genre> for player <player>{player_name}</player>.Construct a scenario and give the player decisions to make.Use the tools to manage the player's inventory as items are obtained or lost.When adding, removing or updating items in the inventory, always list items to check the current state,and be careful to match item names exactly. Item names in the inventory must be Title Case.Ensure you specify a suitable emoji when adding items if available.When starting a game, populate the inventory with a few initial items. Items should be a key part of the narrative.Keep responses under 100 words.""", tools=[*mcp_client.list_tools_sync()], )
This configures the following:
- Extracting the player, genre and actions from the agent payload
- Constructing a client which the Agent can use to invoke our MCP server with SigV4 Authentication
- Constructing the agent with a system prompt and the MCP server’s tools
Deployment and testing
Section titled “Deployment and testing”First, lets build the codebase:
pnpm nx run-many --target build --all
yarn nx run-many --target build --all
npx nx run-many --target build --all
bunx nx run-many --target build --all
Your application can now be deployed by running the following command:
pnpm nx deploy infra dungeon-adventure-infra-sandbox/*
yarn nx deploy infra dungeon-adventure-infra-sandbox/*
npx nx deploy infra dungeon-adventure-infra-sandbox/*
bunx nx deploy infra dungeon-adventure-infra-sandbox/*
This deployment will take around 2 minutes to complete.
Once the deployment completes, you should see some outputs similar to the following (some values have been redacted):
dungeon-adventure-infra-sandbox-Applicationdungeon-adventure-infra-sandbox-Application: deploying... [2/2]
✅ dungeon-adventure-infra-sandbox-Application
✨ Deployment time: 354s
Outputs:dungeon-adventure-infra-sandbox-Application.ElectroDbTableTableNameXXX = dungeon-adventure-infra-sandbox-Application-ElectroDbTableXXX-YYYdungeon-adventure-infra-sandbox-Application.GameApiEndpointXXX = https://xxx.execute-api.region.amazonaws.com/prod/dungeon-adventure-infra-sandbox-Application.GameUIDistributionDomainNameXXX = xxx.cloudfront.netdungeon-adventure-infra-sandbox-Application.InventoryMcpArn = arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventureventoryMcpServerXXXX-YYYYdungeon-adventure-infra-sandbox-Application.StoryAgentArn = arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventurecationStoryAgentXXXX-YYYYdungeon-adventure-infra-sandbox-Application.UserIdentityUserIdentityIdentityPoolIdXXX = region:xxxdungeon-adventure-infra-sandbox-Application.UserIdentityUserIdentityUserPoolIdXXX = region_xxx
We can test our API by either:
- Starting a local instance of the Agent server and invoking it using
curl
. - Calling the deployed API using curl with a JWT token.
Start your local Agent server by running the following command:
PORT=9999 INVENTORY_MCP_ARN=arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventureventoryMcpServerXXXX-YYYY AWS_REGION=<region> pnpm nx run dungeon_adventure.story:agent-serve
PORT=9999 INVENTORY_MCP_ARN=arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventureventoryMcpServerXXXX-YYYY AWS_REGION=<region> yarn nx run dungeon_adventure.story:agent-serve
PORT=9999 INVENTORY_MCP_ARN=arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventureventoryMcpServerXXXX-YYYY AWS_REGION=<region> npx nx run dungeon_adventure.story:agent-serve
PORT=9999 INVENTORY_MCP_ARN=arn:aws:bedrock-agentcore:region:xxxxxxx:runtime/dungeonadventureventoryMcpServerXXXX-YYYY AWS_REGION=<region> bunx nx run dungeon_adventure.story:agent-serve
Once the Agent server is up and running (you won’t see any output!), call it by running the following command:
curl -N -X POST http://127.0.0.1:9999/invocations \ -d '{"genre":"superhero", "actions":[], "playerName":"UnnamedHero"}' \ -H "Content-Type: application/json" \ -H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: abcdefghijklmnopqrstuvwxyz-123456789"
To test the deployed agent, you’ll need to authenticate with Cognito and obtain a JWT token. First, set up your environment variables:
# Set your Cognito User Pool ID and Client ID from the CDK outputsexport POOL_ID="<UserPoolId from CDK outputs>"export CLIENT_ID="<UserPoolClientId from CDK outputs>"export REGION="<your-region>"
Create a test user and obtain an authentication token:
# Create Useraws cognito-idp admin-create-user \ --user-pool-id $POOL_ID \ --username "testuser" \ --temporary-password "TempPass123!" \ --region $REGION \ --message-action SUPPRESS > /dev/null
# Set Permanent Password (replace with something more secure!)aws cognito-idp admin-set-user-password \ --user-pool-id $POOL_ID \ --username "testuser" \ --password "PermanentPass123!" \ --region $REGION \ --permanent > /dev/null
# Authenticate User and capture ID Tokenexport BEARER_TOKEN=$(aws cognito-idp initiate-auth \ --client-id "$CLIENT_ID" \ --auth-flow USER_PASSWORD_AUTH \ --auth-parameters USERNAME='testuser',PASSWORD='PermanentPass123!' \ --region $REGION | jq -r '.AuthenticationResult.IdToken')
Now invoke the deployed agent using the Bedrock AgentCore runtime URL:
# Set the Story Agent ARN from CDK outputsexport AGENT_ARN="<StoryAgentArn from CDK outputs>"
# URL-encode the ARNexport ENCODED_ARN=$(echo $AGENT_ARN | sed 's/:/%3A/g' | sed 's/\//%2F/g')
# Construct the invocation URLexport MCP_URL="https://bedrock-agentcore.$REGION.amazonaws.com/runtimes/$ENCODED_ARN/invocations?qualifier=DEFAULT"
# Invoke the agentcurl -N -X POST "$MCP_URL" \ -H "authorization: Bearer $BEARER_TOKEN" \ -H "Content-Type: application/json" \ -H "X-Amzn-Bedrock-AgentCore-Runtime-Session-Id: abcdefghijklmnopqrstuvwxyz-123456789" \ -d '{"genre":"superhero", "actions":[], "playerName":"UnnamedHero"}'
If the command executes successfully, you should see events being streamed similar to:
data: {"init_event_loop": true}
data: {"start": true}
data: {"start_event_loop": true}
data: {"event": {"messageStart": {"role": "assistant"}}}
data: {"event": {"contentBlockDelta": {"delta": {"text": "Welcome"}, "contentBlockIndex": 0}}}
...
Congratulations. You have built and deployed your first Strands Agent on Bedrock AgentCore Runtime! 🎉🎉🎉