bedrock-agentcore-starter-toolkit 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of bedrock-agentcore-starter-toolkit might be problematic. Click here for more details.

Files changed (50) hide show
  1. bedrock_agentcore_starter_toolkit/__init__.py +5 -0
  2. bedrock_agentcore_starter_toolkit/cli/cli.py +32 -0
  3. bedrock_agentcore_starter_toolkit/cli/common.py +44 -0
  4. bedrock_agentcore_starter_toolkit/cli/gateway/__init__.py +1 -0
  5. bedrock_agentcore_starter_toolkit/cli/gateway/commands.py +88 -0
  6. bedrock_agentcore_starter_toolkit/cli/runtime/__init__.py +1 -0
  7. bedrock_agentcore_starter_toolkit/cli/runtime/commands.py +651 -0
  8. bedrock_agentcore_starter_toolkit/cli/runtime/configuration_manager.py +133 -0
  9. bedrock_agentcore_starter_toolkit/notebook/__init__.py +5 -0
  10. bedrock_agentcore_starter_toolkit/notebook/runtime/__init__.py +1 -0
  11. bedrock_agentcore_starter_toolkit/notebook/runtime/bedrock_agentcore.py +239 -0
  12. bedrock_agentcore_starter_toolkit/operations/__init__.py +1 -0
  13. bedrock_agentcore_starter_toolkit/operations/gateway/README.md +277 -0
  14. bedrock_agentcore_starter_toolkit/operations/gateway/__init__.py +6 -0
  15. bedrock_agentcore_starter_toolkit/operations/gateway/client.py +456 -0
  16. bedrock_agentcore_starter_toolkit/operations/gateway/constants.py +152 -0
  17. bedrock_agentcore_starter_toolkit/operations/gateway/create_lambda.py +85 -0
  18. bedrock_agentcore_starter_toolkit/operations/gateway/create_role.py +90 -0
  19. bedrock_agentcore_starter_toolkit/operations/gateway/exceptions.py +13 -0
  20. bedrock_agentcore_starter_toolkit/operations/runtime/__init__.py +26 -0
  21. bedrock_agentcore_starter_toolkit/operations/runtime/configure.py +241 -0
  22. bedrock_agentcore_starter_toolkit/operations/runtime/create_role.py +404 -0
  23. bedrock_agentcore_starter_toolkit/operations/runtime/invoke.py +129 -0
  24. bedrock_agentcore_starter_toolkit/operations/runtime/launch.py +439 -0
  25. bedrock_agentcore_starter_toolkit/operations/runtime/models.py +79 -0
  26. bedrock_agentcore_starter_toolkit/operations/runtime/status.py +66 -0
  27. bedrock_agentcore_starter_toolkit/services/codebuild.py +332 -0
  28. bedrock_agentcore_starter_toolkit/services/ecr.py +84 -0
  29. bedrock_agentcore_starter_toolkit/services/runtime.py +473 -0
  30. bedrock_agentcore_starter_toolkit/utils/endpoints.py +32 -0
  31. bedrock_agentcore_starter_toolkit/utils/logging_config.py +72 -0
  32. bedrock_agentcore_starter_toolkit/utils/runtime/config.py +129 -0
  33. bedrock_agentcore_starter_toolkit/utils/runtime/container.py +310 -0
  34. bedrock_agentcore_starter_toolkit/utils/runtime/entrypoint.py +197 -0
  35. bedrock_agentcore_starter_toolkit/utils/runtime/logs.py +33 -0
  36. bedrock_agentcore_starter_toolkit/utils/runtime/policy_template.py +74 -0
  37. bedrock_agentcore_starter_toolkit/utils/runtime/schema.py +151 -0
  38. bedrock_agentcore_starter_toolkit/utils/runtime/templates/Dockerfile.j2 +44 -0
  39. bedrock_agentcore_starter_toolkit/utils/runtime/templates/dockerignore.template +68 -0
  40. bedrock_agentcore_starter_toolkit/utils/runtime/templates/execution_role_policy.json.j2 +98 -0
  41. bedrock_agentcore_starter_toolkit/utils/runtime/templates/execution_role_trust_policy.json.j2 +21 -0
  42. bedrock_agentcore_starter_toolkit-0.1.1.dist-info/METADATA +137 -0
  43. bedrock_agentcore_starter_toolkit-0.1.1.dist-info/RECORD +47 -0
  44. bedrock_agentcore_starter_toolkit-0.1.1.dist-info/entry_points.txt +2 -0
  45. bedrock_agentcore_starter_toolkit-0.1.1.dist-info/licenses/NOTICE.txt +190 -0
  46. bedrock_agentcore_starter_toolkit/init.py +0 -3
  47. bedrock_agentcore_starter_toolkit-0.0.1.dist-info/METADATA +0 -26
  48. bedrock_agentcore_starter_toolkit-0.0.1.dist-info/RECORD +0 -5
  49. {bedrock_agentcore_starter_toolkit-0.0.1.dist-info → bedrock_agentcore_starter_toolkit-0.1.1.dist-info}/WHEEL +0 -0
  50. /bedrock_agentcore_starter_toolkit-0.0.1.dist-info/licenses/LICENSE → /bedrock_agentcore_starter_toolkit-0.1.1.dist-info/licenses/LICENSE.txt +0 -0
@@ -0,0 +1,456 @@
1
+ """Client for interacting with Bedrock AgentCore Gateway services."""
2
+
3
+ import json
4
+ import logging
5
+ import time
6
+ import urllib.parse
7
+ import uuid
8
+ from typing import Any, Dict, Optional
9
+
10
+ import boto3
11
+ import urllib3
12
+
13
+ from .constants import (
14
+ API_MODEL_BUCKETS,
15
+ CREATE_OPENAPI_TARGET_INVALID_CREDENTIALS_SHAPE_EXCEPTION_MESSAGE,
16
+ LAMBDA_CONFIG,
17
+ )
18
+ from .create_lambda import create_test_lambda
19
+ from .create_role import create_gateway_execution_role
20
+ from .exceptions import GatewaySetupException
21
+
22
+
23
+ class GatewayClient:
24
+ """High-level client for Bedrock AgentCore Gateway operations."""
25
+
26
+ def __init__(self, region_name: Optional[str] = None, endpoint_url: Optional[str] = None):
27
+ """Initialize the Gateway client.
28
+
29
+ Args:
30
+ region_name: AWS region name (defaults to us-west-2)
31
+ endpoint_url: Custom endpoint URL for the Gateway service
32
+ """
33
+ self.region = region_name or "us-west-2"
34
+
35
+ if endpoint_url:
36
+ self.client = boto3.client(
37
+ "bedrock-agentcore-control",
38
+ region_name=self.region,
39
+ endpoint_url=endpoint_url,
40
+ )
41
+ else:
42
+ self.client = boto3.client("bedrock-agentcore-control", region_name=self.region)
43
+
44
+ self.session = boto3.Session(region_name=self.region)
45
+
46
+ # Initialize the logger
47
+ self.logger = logging.getLogger("bedrock_agentcore.gateway")
48
+ if not self.logger.handlers:
49
+ handler = logging.StreamHandler()
50
+ formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
51
+ handler.setFormatter(formatter)
52
+ self.logger.addHandler(handler)
53
+ self.logger.setLevel(logging.INFO)
54
+
55
+ def create_mcp_gateway(
56
+ self,
57
+ name=None,
58
+ role_arn=None,
59
+ authorizer_config=None,
60
+ enable_semantic_search=True,
61
+ ) -> dict:
62
+ """Creates an MCP Gateway.
63
+
64
+ :param name: optional - the name of the gateway (defaults to TestGateway).
65
+ :param role_arn: optional - the role arn to use (creates one if none provided).
66
+ :param authorizer_config: optional - the authorizer config (will create one if none provided).
67
+ :param enable_semantic_search: optional - whether to enable search tool (defaults to True).
68
+ :return: the created Gateway.
69
+ """
70
+ if not name:
71
+ name = f"TestGateway{GatewayClient.generate_random_id()}"
72
+ if not role_arn:
73
+ self.logger.info("Role not provided, creating an execution role to use")
74
+ role_arn = create_gateway_execution_role(self.session, self.logger)
75
+ self.logger.info("✓ Successfully created execution role for Gateway")
76
+ if not authorizer_config:
77
+ self.logger.info("Authorizer config not provided, creating an authorizer to use")
78
+ cognito_result = self.create_oauth_authorizer_with_cognito(name)
79
+ self.logger.info("✓ Successfully created authorizer for Gateway")
80
+ authorizer_config = cognito_result["authorizer_config"]
81
+ create_request = {
82
+ "name": name,
83
+ "roleArn": role_arn,
84
+ "protocolType": "MCP",
85
+ "authorizerType": "CUSTOM_JWT",
86
+ "authorizerConfiguration": authorizer_config,
87
+ }
88
+ if enable_semantic_search:
89
+ create_request["protocolConfiguration"] = {"mcp": {"searchType": "SEMANTIC"}}
90
+ self.logger.info("Creating Gateway")
91
+ self.logger.debug("Creating gateway with params: %s", json.dumps(create_request, indent=2))
92
+ gateway = self.client.create_gateway(**create_request)
93
+ self.logger.info("✓ Created Gateway: %s", gateway["gatewayArn"])
94
+ self.logger.info(" Gateway URL: %s", gateway["gatewayUrl"])
95
+
96
+ # Wait for gateway to be ready
97
+ self.logger.info(" Waiting for Gateway to be ready...")
98
+ self.__wait_for_ready(
99
+ method=self.client.get_gateway,
100
+ identifiers={"gatewayIdentifier": gateway["gatewayId"]},
101
+ resource_name="Gateway",
102
+ )
103
+ self.logger.info("\n✅Gateway is ready")
104
+ return gateway
105
+
106
+ def create_mcp_gateway_target(
107
+ self,
108
+ gateway: dict,
109
+ name=None,
110
+ target_type="lambda",
111
+ target_payload=None,
112
+ credentials=None,
113
+ ) -> dict:
114
+ """Creates an MCP Gateway Target.
115
+
116
+ :param gateway: the gateway (output of create_mcp_gateway or calling get_gateway() with boto3 client).
117
+ :param name: optional - the name of the target (defaults to TestGatewayTarget).
118
+ :param target_type: optional - the type of the target e.g. one of "lambda" |
119
+ "openApiSchema" | "smithyModel" (defaults to "lambda").
120
+ :param target_payload: only required for openApiSchema target - the specification of that target.
121
+ :param credentials: only use with openApiSchema target - the credentials for calling this target
122
+ (api key or oauth2).
123
+ :return: the created target.
124
+ """
125
+ # there is no name, create one
126
+ if not name:
127
+ name = f"TestGatewayTarget{GatewayClient.generate_random_id()}"
128
+ # instantiate base creation request
129
+ create_request = {
130
+ "gatewayIdentifier": gateway["gatewayId"],
131
+ "name": name,
132
+ "targetConfiguration": {"mcp": {target_type: target_payload}},
133
+ }
134
+ # handle cases of missing target payloads across smithy and lambda (default to something)
135
+ if not target_payload and target_type == "lambda":
136
+ create_request |= self.__handle_lambda_target_creation(gateway["roleArn"])
137
+ if not target_payload and target_type == "smithyModel":
138
+ region_bucket = API_MODEL_BUCKETS.get(self.region)
139
+ if not region_bucket:
140
+ raise Exception(
141
+ "Automatic smithyModel creation is not supported in this region. "
142
+ "Please try again by explicitly providing a smithyModel via targetPayload."
143
+ )
144
+ create_request |= {
145
+ "targetConfiguration": {
146
+ "mcp": {"smithyModel": {"s3": {"uri": f"s3://{region_bucket}/dynamodb-smithy.json"}}}
147
+ },
148
+ "credentialProviderConfigurations": [{"credentialProviderType": "GATEWAY_IAM_ROLE"}],
149
+ }
150
+ # open api schemas need a target config with them
151
+ if not target_payload and target_type == "openApiSchema":
152
+ raise Exception("You must provide a target configuration for your OpenAPI specification.")
153
+ # handle open api schema
154
+ if target_type == "openApiSchema":
155
+ create_request |= self.__handle_openapi_target_credential_provider_creation(
156
+ name=name, credentials=credentials
157
+ )
158
+ # create the target
159
+ self.logger.info("Creating Target")
160
+ self.logger.info(create_request)
161
+ self.logger.debug("Creating target with params: %s", json.dumps(create_request, indent=2))
162
+ target = self.client.create_gateway_target(**create_request)
163
+ self.logger.info("✓ Added target successfully (ID: %s)", target["targetId"])
164
+ self.logger.info(" Waiting for target to be ready...")
165
+ # poll till target is in READY state
166
+ self.__wait_for_ready(
167
+ method=self.client.get_gateway_target,
168
+ identifiers={
169
+ "gatewayIdentifier": gateway["gatewayId"],
170
+ "targetId": target["targetId"],
171
+ },
172
+ resource_name="Target",
173
+ )
174
+ self.logger.info("\n✅Target is ready")
175
+ return target
176
+
177
+ def __handle_lambda_target_creation(self, role_arn: str) -> Dict[str, Any]:
178
+ """Create a test lambda.
179
+
180
+ :return: the targetConfiguration for the Lambda.
181
+ """
182
+ lambda_arn = create_test_lambda(self.session, logger=self.logger, gateway_role_arn=role_arn)
183
+
184
+ return {
185
+ "targetConfiguration": {"mcp": {"lambda": {"lambdaArn": lambda_arn, "toolSchema": LAMBDA_CONFIG}}},
186
+ "credentialProviderConfigurations": [{"credentialProviderType": "GATEWAY_IAM_ROLE"}],
187
+ }
188
+
189
+ def __handle_openapi_target_credential_provider_creation(
190
+ self, name: str, credentials: Dict[str, Any]
191
+ ) -> Dict[str, Any]:
192
+ """Generate the credential provider config for open api target.
193
+
194
+ :param name: the name of the target.
195
+ :param credentials: credentials to use in setting up this target.
196
+ :return: the credential provider config.
197
+ """
198
+ acps = self.session.client(service_name="bedrock-agentcore-control")
199
+ if "api_key" in credentials:
200
+ self.logger.info("Creating credential provider")
201
+ credential_provider = acps.create_api_key_credential_provider(
202
+ name=f"{name}-ApiKey-{self.generate_random_id()}",
203
+ apiKey=credentials["api_key"],
204
+ )
205
+ self.logger.info(
206
+ "✓ Added credential provider successfully (ARN: %s)",
207
+ credential_provider["credentialProviderArn"],
208
+ )
209
+ target_cred_provider_config = {
210
+ "credentialProviderType": "API_KEY",
211
+ "credentialProvider": {
212
+ "apiKeyCredentialProvider": {
213
+ "providerArn": credential_provider["credentialProviderArn"],
214
+ "credentialLocation": credentials["credential_location"],
215
+ "credentialParameterName": credentials["credential_parameter_name"],
216
+ }
217
+ },
218
+ }
219
+ elif "oauth2_provider_config" in credentials:
220
+ self.logger.info("Creating credential provider")
221
+ credential_provider = acps.create_oauth2_credential_provider(
222
+ name=f"{name}-OAuth-Credentials-{self.generate_random_id()}",
223
+ credentialProviderVendor="CustomOauth2",
224
+ oauth2ProviderConfigInput=credentials["oauth2_provider_config"],
225
+ )
226
+ self.logger.info(
227
+ "✓ Added credential provider successfully (ARN: %s)",
228
+ credential_provider["credentialProviderArn"],
229
+ )
230
+ target_cred_provider_config = {
231
+ "credentialProviderType": "OAUTH",
232
+ "credentialProvider": {
233
+ "oauthCredentialProvider": {
234
+ "providerArn": credential_provider["credentialProviderArn"],
235
+ "scopes": credentials.get("scopes", []),
236
+ }
237
+ },
238
+ }
239
+ else:
240
+ raise Exception(CREATE_OPENAPI_TARGET_INVALID_CREDENTIALS_SHAPE_EXCEPTION_MESSAGE)
241
+ return {"credentialProviderConfigurations": [target_cred_provider_config]}
242
+
243
+ @staticmethod
244
+ def __wait_for_ready(resource_name, method, identifiers, max_attempts: int = 30, delay: int = 2) -> None:
245
+ """Wait for the resource to be ready.
246
+
247
+ :param resource_name: the name of the resource.
248
+ :param method: the method to be invoked.
249
+ :param identifiers: the identifiers to fetch the resource (e.g. gateway id, target id).
250
+ :param max_attempts: the maximum number of times to poll.
251
+ :param delay: time delay in between polls.
252
+ :return:
253
+ """
254
+ attempts = 0
255
+ while True:
256
+ response = method(**identifiers)
257
+ status = response.get("status", "UNKNOWN")
258
+ if not status == "CREATING":
259
+ break
260
+ time.sleep(delay)
261
+ attempts += 1
262
+ if attempts >= max_attempts:
263
+ raise TimeoutError(f"{resource_name} not ready after {max_attempts} attempts")
264
+ if status == "READY":
265
+ return
266
+ else:
267
+ raise Exception(f"{resource_name} failed: {response}")
268
+
269
+ # Generate unique IDs
270
+ @staticmethod
271
+ def generate_random_id():
272
+ """Generate a random ID for Cognito resources."""
273
+ return str(uuid.uuid4())[:8]
274
+
275
+ def create_oauth_authorizer_with_cognito(self, gateway_name: str) -> Dict[str, Any]:
276
+ """Creates Cognito OAuth authorization server.
277
+
278
+ :param gateway_name: the name of the gateway being created for use in naming Cognito resources.
279
+ :return: dictionary with details of the authorization server, client id, and client secret.
280
+ """
281
+ self.logger.info("Starting EZ Auth setup: Creating Cognito resources...")
282
+
283
+ cognito_client = self.session.client("cognito-idp")
284
+
285
+ try:
286
+ # 1. Create User Pool
287
+ pool_name = f"agentcore-gateway-{GatewayClient.generate_random_id()}"
288
+ user_pool_response = cognito_client.create_user_pool(PoolName=pool_name)
289
+ user_pool_id = user_pool_response["UserPool"]["Id"]
290
+ self.logger.info(" ✓ Created User Pool: %s", user_pool_id)
291
+
292
+ # 2. Create User Pool Domain
293
+ domain_prefix = f"agentcore-{GatewayClient.generate_random_id()}"
294
+ cognito_client.create_user_pool_domain(Domain=domain_prefix, UserPoolId=user_pool_id)
295
+ self.logger.info(" ✓ Created domain: %s", domain_prefix)
296
+
297
+ # Wait for domain to be available
298
+ self.logger.info(" ⏳ Waiting for domain to be available...")
299
+ domain_ready = False
300
+ for _ in range(30): # Wait up to 30 seconds
301
+ try:
302
+ response = cognito_client.describe_user_pool_domain(Domain=domain_prefix)
303
+ if response.get("DomainDescription", {}).get("Status") == "ACTIVE":
304
+ domain_ready = True
305
+ break
306
+ except cognito_client.exceptions.ClientError as e:
307
+ self.logger.debug("Domain not yet active: %s", e)
308
+ pass
309
+ time.sleep(1)
310
+
311
+ if not domain_ready:
312
+ self.logger.warning(" ⚠️ Domain may not be fully available yet")
313
+ else:
314
+ self.logger.info(" ✓ Domain is active")
315
+
316
+ # 3. Create Resource Server
317
+ # Using gateway_name as the resource server identifier
318
+ resource_server_id = gateway_name
319
+ gateway_scopes = [
320
+ {
321
+ "ScopeName": "invoke", # Just 'invoke', will be formatted as resource_server_id/invoke
322
+ "ScopeDescription": "Scope for invoking the agentcore gateway",
323
+ }
324
+ ]
325
+
326
+ cognito_client.create_resource_server(
327
+ UserPoolId=user_pool_id,
328
+ Identifier=resource_server_id,
329
+ Name=gateway_name,
330
+ Scopes=gateway_scopes,
331
+ )
332
+ self.logger.info(" ✓ Created resource server: %s", resource_server_id)
333
+
334
+ # 4. Create User Pool Client
335
+ client_name = f"agentcore-client-{GatewayClient.generate_random_id()}"
336
+
337
+ # Format scopes as {resource_server_id}/{scope_name} as per the update
338
+ scope_names = [f"{resource_server_id}/{scope['ScopeName']}" for scope in gateway_scopes]
339
+ # This results in: "gateway_name/invoke"
340
+
341
+ user_pool_client_response = cognito_client.create_user_pool_client(
342
+ UserPoolId=user_pool_id,
343
+ ClientName=client_name,
344
+ GenerateSecret=True,
345
+ AllowedOAuthFlows=["client_credentials"],
346
+ AllowedOAuthScopes=scope_names, # Using the formatted scope names
347
+ AllowedOAuthFlowsUserPoolClient=True,
348
+ SupportedIdentityProviders=["COGNITO"],
349
+ )
350
+
351
+ client_id = user_pool_client_response["UserPoolClient"]["ClientId"]
352
+ client_secret = user_pool_client_response["UserPoolClient"]["ClientSecret"]
353
+ self.logger.info(" ✓ Created client: %s", client_id)
354
+
355
+ # Build the return structure
356
+ discovery_url = (
357
+ f"https://cognito-idp.{self.region}.amazonaws.com/{user_pool_id}/.well-known/openid-configuration"
358
+ )
359
+
360
+ # Format for AgentCore Gateway authorizer config
361
+ custom_jwt_authorizer = {
362
+ "customJWTAuthorizer": {
363
+ "allowedClients": [client_id],
364
+ "discoveryUrl": discovery_url,
365
+ }
366
+ }
367
+
368
+ result = {
369
+ "authorizer_config": custom_jwt_authorizer,
370
+ "client_info": {
371
+ "client_id": client_id,
372
+ "client_secret": client_secret,
373
+ "user_pool_id": user_pool_id,
374
+ "token_endpoint": f"https://{domain_prefix}.auth.{self.region}.amazoncognito.com/oauth2/token",
375
+ "scope": scope_names[0],
376
+ "domain_prefix": domain_prefix,
377
+ },
378
+ }
379
+
380
+ if domain_prefix:
381
+ self.logger.info(
382
+ " ⏳ Waiting for DNS propagation of domain: %s.auth.%s.amazoncognito.com",
383
+ domain_prefix,
384
+ self.region,
385
+ )
386
+ # Wait for DNS to propagate (60 seconds)
387
+ time.sleep(60)
388
+
389
+ self.logger.info("✓ EZ Auth setup complete!")
390
+ return result
391
+
392
+ except Exception as e:
393
+ raise GatewaySetupException(f"Failed to create Cognito resources: {e}") from e
394
+
395
+ def get_access_token_for_cognito(self, client_info: Dict[str, Any]) -> str:
396
+ """Get OAuth token using client credentials flow.
397
+
398
+ :param client_info: credentials and context needed to get the access token
399
+ (output of the create_oauth_authorizer_with_cognito method).
400
+ :return: the access token.
401
+ """
402
+ self.logger.info("Fetching test token from Cognito...")
403
+
404
+ max_retries = 5
405
+ retry_delay = 10
406
+
407
+ for attempt in range(max_retries):
408
+ try:
409
+ # Make HTTP request to token endpoint
410
+ http = urllib3.PoolManager()
411
+
412
+ # Prepare the form data
413
+ form_data = {
414
+ "grant_type": "client_credentials",
415
+ "client_id": client_info["client_id"],
416
+ "client_secret": client_info["client_secret"],
417
+ "scope": client_info["scope"],
418
+ }
419
+
420
+ # Log token endpoint for debugging
421
+ self.logger.info(
422
+ " Attempting to connect to token endpoint: %s",
423
+ client_info["token_endpoint"],
424
+ )
425
+
426
+ response = http.request(
427
+ "POST",
428
+ client_info["token_endpoint"],
429
+ body=urllib.parse.urlencode(form_data),
430
+ headers={"Content-Type": "application/x-www-form-urlencoded"},
431
+ timeout=10.0, # Add explicit timeout
432
+ retries=False,
433
+ )
434
+
435
+ if response.status != 200:
436
+ raise GatewaySetupException(f"Token request failed: {response.data.decode()}")
437
+
438
+ token_data = json.loads(response.data.decode())
439
+ access_token = token_data["access_token"]
440
+
441
+ self.logger.info("✓ Got test token successfully")
442
+ return access_token
443
+
444
+ except urllib3.exceptions.MaxRetryError as e:
445
+ if "NameResolutionError" in str(e) and attempt < max_retries - 1:
446
+ self.logger.warning(
447
+ " Domain not yet resolvable (attempt %s/%s). Waiting %s seconds...",
448
+ attempt + 1,
449
+ max_retries,
450
+ retry_delay,
451
+ )
452
+ time.sleep(retry_delay)
453
+ continue
454
+ raise GatewaySetupException(f"Failed to get test token: {e}") from e
455
+ except Exception as e:
456
+ raise GatewaySetupException(f"Failed to get test token: {e}") from e
@@ -0,0 +1,152 @@
1
+ """Constants for use in Bedrock AgentCore Gateway."""
2
+
3
+ API_MODEL_BUCKETS = {
4
+ "ap-southeast-2": "amazonbedrockagentcore-built-sampleschemas455e0815-yigvs4je21kx",
5
+ "us-west-2": "amazonbedrockagentcore-built-sampleschemas455e0815-omxvr7ybq9g8",
6
+ "eu-central-1": "amazonbedrockagentcore-built-sampleschemas455e0815-egpctdjskcrf",
7
+ "us-east-1": "amazonbedrockagentcore-built-sampleschemas455e0815-oj7jujcd8xiu",
8
+ }
9
+
10
+ CREATE_OPENAPI_TARGET_INVALID_CREDENTIALS_SHAPE_EXCEPTION_MESSAGE = """
11
+ Provided credentials object was not formatted correctly. Correct formats below:
12
+
13
+ API Key:
14
+ {
15
+ "api_key": "<key>",
16
+ "credential_location": "HEADER | BODY",
17
+ "credential_parameter_name": "<name of parameter>"
18
+ }
19
+
20
+ OAuth:
21
+ {
22
+ "oauth2_provider_config": {
23
+ "customOauth2ProviderConfig": {
24
+ <same as the agentcredentialprovider customOauth2ProviderConfig object>
25
+ }
26
+ }
27
+ }
28
+
29
+ Example for OAuth:
30
+ {
31
+ "oauth2_provider_config": {
32
+ "customOauth2ProviderConfig": {
33
+ "oauthDiscovery" : {
34
+ "authorizationServerMetadata" : {
35
+ "issuer" : "< issuer endpoint >",
36
+ "authorizationEndpoint" : "< authorization endpoint >",
37
+ "tokenEndpoint" : "< token endpoint >"
38
+ }
39
+ },
40
+ "clientId" : "< client id >",
41
+ "clientSecret" : "< client secret >"
42
+ }
43
+ }
44
+ }
45
+ """
46
+
47
+ BEDROCK_AGENTCORE_TRUST_POLICY = {
48
+ "Version": "2012-10-17",
49
+ "Statement": [
50
+ {
51
+ "Effect": "Allow",
52
+ "Principal": {"Service": "bedrock-agentcore.amazonaws.com"},
53
+ "Action": "sts:AssumeRole",
54
+ }
55
+ ],
56
+ }
57
+
58
+ AGENTCORE_FULL_ACCESS = {
59
+ "Version": "2012-10-17",
60
+ "Statement": [
61
+ {
62
+ "Sid": "BedrockAgentCoreFullAccess",
63
+ "Effect": "Allow",
64
+ "Action": ["bedrock-agentcore:*"],
65
+ "Resource": "arn:aws:bedrock-agentcore:*:*:*",
66
+ },
67
+ {
68
+ "Sid": "GetSecretValue",
69
+ "Effect": "Allow",
70
+ "Action": ["secretsmanager:GetSecretValue"],
71
+ "Resource": "*",
72
+ },
73
+ {
74
+ "Sid": "LambdaInvokeAccess",
75
+ "Effect": "Allow",
76
+ "Action": ["lambda:InvokeFunction"],
77
+ "Resource": "arn:aws:lambda:*:*:function:*",
78
+ },
79
+ ],
80
+ }
81
+
82
+ POLICIES_TO_CREATE = [("BedrockAgentCoreGatewayStarterFullAccess", AGENTCORE_FULL_ACCESS)]
83
+
84
+ POLICIES = {
85
+ "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess",
86
+ "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess",
87
+ }
88
+
89
+ LAMBDA_FUNCTION_CODE = """
90
+ import json
91
+
92
+ def lambda_handler(event, context):
93
+ # Extract tool name from context
94
+ tool_name = context.client_context.custom.get('bedrockAgentCoreToolName', 'unknown')
95
+
96
+ if 'get_weather' in tool_name:
97
+ return {
98
+ 'statusCode': 200,
99
+ 'body': json.dumps({
100
+ 'location': event.get('location', 'Unknown'),
101
+ 'temperature': '72°F',
102
+ 'conditions': 'Sunny'
103
+ })
104
+ }
105
+ elif 'get_time' in tool_name:
106
+ return {
107
+ 'statusCode': 200,
108
+ 'body': json.dumps({
109
+ 'timezone': event.get('timezone', 'UTC'),
110
+ 'time': '2:30 PM'
111
+ })
112
+ }
113
+ else:
114
+ return {
115
+ 'statusCode': 200,
116
+ 'body': json.dumps({'message': 'Unknown tool'})
117
+ }
118
+ """
119
+
120
+ LAMBDA_TRUST_POLICY = {
121
+ "Version": "2012-10-17",
122
+ "Statement": [
123
+ {
124
+ "Effect": "Allow",
125
+ "Principal": {"Service": "lambda.amazonaws.com"},
126
+ "Action": "sts:AssumeRole",
127
+ }
128
+ ],
129
+ }
130
+
131
+ LAMBDA_CONFIG = {
132
+ "inlinePayload": [
133
+ {
134
+ "name": "get_weather",
135
+ "description": "Get weather for a location",
136
+ "inputSchema": {
137
+ "type": "object",
138
+ "properties": {"location": {"type": "string"}},
139
+ "required": ["location"],
140
+ },
141
+ },
142
+ {
143
+ "name": "get_time",
144
+ "description": "Get time for a timezone",
145
+ "inputSchema": {
146
+ "type": "object",
147
+ "properties": {"timezone": {"type": "string"}},
148
+ "required": ["timezone"],
149
+ },
150
+ },
151
+ ],
152
+ }
@@ -0,0 +1,85 @@
1
+ """Creates a Lambda function to use as a Bedrock AgentCore Gateway Target."""
2
+
3
+ import io
4
+ import json
5
+ import logging
6
+ import zipfile
7
+
8
+ from boto3 import Session
9
+
10
+ from ...operations.gateway.constants import (
11
+ LAMBDA_FUNCTION_CODE,
12
+ LAMBDA_TRUST_POLICY,
13
+ )
14
+
15
+
16
+ def create_test_lambda(session: Session, logger: logging.Logger, gateway_role_arn: str) -> str:
17
+ """Create a test Lambda function.
18
+
19
+ :param region_name: the name of the region to create in.
20
+ :param logger: instance of a logger.
21
+ :param gateway_role_arn: the execution role arn of the gateway this lambda is going to be used with.
22
+ :return: the lambda arn
23
+ """
24
+ lambda_client = session.client("lambda")
25
+ iam = session.client("iam")
26
+ function_name = "AgentCoreLambdaTestFunction"
27
+ role_name = "AgentCoreTestLambdaRole"
28
+
29
+ # Create zip file
30
+ zip_buffer = io.BytesIO()
31
+ with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zip_file:
32
+ zip_file.writestr("lambda_function.py", LAMBDA_FUNCTION_CODE)
33
+ zip_buffer.seek(0)
34
+
35
+ # Create Lambda execution role
36
+
37
+ try:
38
+ role_response = iam.create_role(RoleName=role_name, AssumeRolePolicyDocument=json.dumps(LAMBDA_TRUST_POLICY))
39
+
40
+ iam.attach_role_policy(
41
+ RoleName=role_name,
42
+ PolicyArn="arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
43
+ )
44
+
45
+ role_arn = role_response["Role"]["Arn"]
46
+ logger.info("✓ Created Lambda execution role: %s", role_arn)
47
+
48
+ # Wait a bit for role to propagate
49
+ import time
50
+
51
+ time.sleep(10)
52
+
53
+ except iam.exceptions.EntityAlreadyExistsException:
54
+ role = iam.get_role(RoleName=role_name)
55
+ role_arn = role["Role"]["Arn"]
56
+
57
+ # Create Lambda function
58
+ try:
59
+ response = lambda_client.create_function(
60
+ FunctionName=function_name,
61
+ Runtime="python3.9",
62
+ Role=role_arn,
63
+ Handler="lambda_function.lambda_handler",
64
+ Code={"ZipFile": zip_buffer.read()},
65
+ Description="Test Lambda for AgentCore Gateway",
66
+ )
67
+
68
+ lambda_arn = response["FunctionArn"]
69
+ logger.info("✓ Created Lambda function: %s", lambda_arn)
70
+ logger.info("✓ Attaching access policy to: %s for %s", lambda_arn, gateway_role_arn)
71
+
72
+ lambda_client.add_permission(
73
+ FunctionName=function_name,
74
+ StatementId="AllowAgentCoreInvoke",
75
+ Action="lambda:InvokeFunction",
76
+ Principal=gateway_role_arn,
77
+ )
78
+ logger.info("✓ Attached permissions for role invocation: %s", lambda_arn)
79
+
80
+ except lambda_client.exceptions.ResourceConflictException:
81
+ response = lambda_client.get_function(FunctionName=function_name)
82
+ lambda_arn = response["Configuration"]["FunctionArn"]
83
+ logger.info("✓ Lambda already exists: %s", lambda_arn)
84
+
85
+ return lambda_arn