diff --git a/agentcore-web-search-cdk/.gitignore b/agentcore-web-search-cdk/.gitignore new file mode 100644 index 0000000000..576a01fe73 --- /dev/null +++ b/agentcore-web-search-cdk/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +cdk.out/ +*.js +*.d.ts +build/ +cdk.context.json diff --git a/agentcore-web-search-cdk/README.md b/agentcore-web-search-cdk/README.md new file mode 100644 index 0000000000..64ba186872 --- /dev/null +++ b/agentcore-web-search-cdk/README.md @@ -0,0 +1,72 @@ +# Amazon Bedrock AgentCore Gateway with Web Search Tool + +This pattern deploys an Amazon Bedrock AgentCore Gateway with a managed Web Search Tool connector target and an AWS Lambda function that invokes the gateway to answer questions using live web data. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/agentcore-web-search-cdk + +Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example. + +## Requirements + +* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources. +* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured +* [Node.js 18+](https://nodejs.org/en/download/) installed +* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed + +## Architecture + +1. **Amazon Bedrock AgentCore Gateway** - Hosts the Web Search Tool as a connector target with IAM authorization +2. **Web Search Tool Connector** - A managed connector that provides live web search capabilities without external API keys +3. **AWS Lambda Function** - Invokes the gateway to perform web searches and return results + +## Deployment Instructions + +1. Install dependencies: + ```bash + npm install + ``` + +2. Build the project: + ```bash + npx tsc + ``` + +3. Deploy the stack (us-east-1 only): + ```bash + npx cdk deploy + ``` + +## How it works + +The Amazon Bedrock AgentCore Gateway provides a unified connectivity layer between agents and tools. The Web Search Tool is a managed connector (`web-search`) that lets agents retrieve information from the live web without any external search service API keys or infrastructure. + +The AWS Lambda function demonstrates invoking the gateway's web search tool by calling the `invoke_tool` API with a query string (max 200 characters) and optional `maxResults` parameter (1-25). + +## Testing + +After deployment, invoke the AWS Lambda function: + +```bash +aws lambda invoke \ + --function-name \ + --payload '{"query": "What is Amazon Bedrock AgentCore?"}' \ + --cli-binary-format raw-in-base64-out \ + output.json && cat output.json +``` + +You can also pass `maxResults` (1-25) in the event payload to control the number of search results returned. + +## Cleanup + +```bash +npx cdk destroy +``` + +## Author + +* **Nithin Chandran R** - [LinkedIn](https://www.linkedin.com/in/nithin-chandran-r/) + +---- +Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/agentcore-web-search-cdk/bin/app.ts b/agentcore-web-search-cdk/bin/app.ts new file mode 100644 index 0000000000..d891dfc179 --- /dev/null +++ b/agentcore-web-search-cdk/bin/app.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env node +import * as cdk from 'aws-cdk-lib'; +import { AgentcoreWebSearchStack } from '../lib/agentcore-web-search-stack'; + +const app = new cdk.App(); +new AgentcoreWebSearchStack(app, 'AgentcoreWebSearchStack', { + env: { region: 'us-east-1' }, +}); diff --git a/agentcore-web-search-cdk/cdk.json b/agentcore-web-search-cdk/cdk.json new file mode 100644 index 0000000000..debd1380e0 --- /dev/null +++ b/agentcore-web-search-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "node build/bin/app.js" +} diff --git a/agentcore-web-search-cdk/example-pattern.json b/agentcore-web-search-cdk/example-pattern.json new file mode 100644 index 0000000000..724c72f172 --- /dev/null +++ b/agentcore-web-search-cdk/example-pattern.json @@ -0,0 +1,85 @@ +{ + "title": "Amazon Bedrock AgentCore Gateway with Web Search Tool", + "description": "This pattern deploys an Amazon Bedrock AgentCore Gateway with a Web Search Tool connector target and an AWS Lambda function that invokes the gateway to perform live web searches.", + "language": "Python", + "level": "200", + "framework": "AWS CDK", + "introBox": { + "headline": "How it works", + "text": [ + "This pattern demonstrates how to use Amazon Bedrock AgentCore Gateway with the managed Web Search Tool connector.", + "The Amazon Bedrock AgentCore Gateway hosts the web search tool as a target, enabling agents to retrieve live web data.", + "An AWS Lambda function invokes the gateway to perform web searches and return results." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/agentcore-web-search-cdk", + "templateURL": "serverless-patterns/agentcore-web-search-cdk", + "projectFolder": "agentcore-web-search-cdk" + } + }, + "resources": { + "bullets": [ + { + "text": "Amazon Bedrock AgentCore Gateway Documentation", + "link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway.html" + }, + { + "text": "Web Search Tool as Connector Target", + "link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-target-connector-web-search-tool.html" + } + ] + }, + "deploy": { + "text": [ + "npx cdk deploy" + ] + }, + "testing": { + "text": [ + "After deployment, invoke the AWS Lambda function with a query:", + "aws lambda invoke --function-name FUNCTION_NAME --payload '{\"query\": \"What is Amazon Bedrock AgentCore?\"}' --cli-binary-format raw-in-base64-out output.json && cat output.json" + ] + }, + "cleanup": { + "text": [ + "npx cdk destroy" + ] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Cloud Engineer", + "linkedin": "nithin-chandran-r" + } + ], + "patternArch": { + "icon1": { + "x": 20, + "y": 50, + "service": "bedrock", + "label": "AgentCore Gateway" + }, + "icon2": { + "x": 50, + "y": 50, + "service": "bedrock", + "label": "Web Search Tool" + }, + "icon3": { + "x": 80, + "y": 50, + "service": "lambda", + "label": "AWS Lambda" + }, + "line1": { + "from": "icon1", + "to": "icon2" + }, + "line2": { + "from": "icon3", + "to": "icon1" + } + } +} diff --git a/agentcore-web-search-cdk/lib/agentcore-web-search-stack.ts b/agentcore-web-search-cdk/lib/agentcore-web-search-stack.ts new file mode 100644 index 0000000000..6276b1c166 --- /dev/null +++ b/agentcore-web-search-cdk/lib/agentcore-web-search-stack.ts @@ -0,0 +1,71 @@ +import * as cdk from 'aws-cdk-lib'; +import * as agentcore from 'aws-cdk-lib/aws-bedrockagentcore'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import path = require('path'); +import { Construct } from 'constructs'; + +export class AgentcoreWebSearchStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Amazon Bedrock AgentCore Gateway with IAM authorization + const gateway = new agentcore.Gateway(this, 'WebSearchGateway', { + gatewayName: 'web-search-gateway', + description: 'Amazon Bedrock AgentCore Gateway with Web Search Tool connector', + authorizerConfiguration: agentcore.GatewayAuthorizer.usingAwsIam(), + }); + + // Web Search connector target using CfnResource (L1) + const webSearchTarget = new cdk.CfnResource(this, 'WebSearchTarget', { + type: 'AWS::BedrockAgentCore::GatewayTarget', + properties: { + GatewayIdentifier: gateway.gatewayId, + Name: 'web-search-target', + TargetConfiguration: { + Mcp: { + Connector: { + Source: { ConnectorId: 'web-search' }, + Configurations: [ + { + Name: 'WebSearch', + ParameterValues: {}, + }, + ], + }, + }, + }, + CredentialProviderConfigurations: [ + { CredentialProviderType: 'GATEWAY_IAM_ROLE' }, + ], + }, + }); + + // Grant the gateway role permission to invoke web search + gateway.role.addToPrincipalPolicy(new iam.PolicyStatement({ + actions: ['bedrock-agentcore:InvokeWebSearch'], + resources: [ + `arn:aws:bedrock-agentcore:${this.region}:aws:tool/web-search.v1`, + ], + })); + + // AWS Lambda function to invoke the gateway + const fn = new lambda.Function(this, 'WebSearchFunction', { + runtime: lambda.Runtime.PYTHON_3_12, + handler: 'index.handler', + code: lambda.Code.fromAsset(path.join(__dirname, '..', '..', 'src', 'handler')), + timeout: cdk.Duration.seconds(30), + environment: { + GATEWAY_ID: gateway.gatewayId, + GATEWAY_URL: gateway.gatewayUrl ?? '', + }, + }); + + // Grant the AWS Lambda function permission to invoke the gateway + gateway.grantInvoke(fn); + + // Outputs + new cdk.CfnOutput(this, 'GatewayId', { value: gateway.gatewayId }); + new cdk.CfnOutput(this, 'FunctionName', { value: fn.functionName }); + } +} diff --git a/agentcore-web-search-cdk/package.json b/agentcore-web-search-cdk/package.json new file mode 100644 index 0000000000..2b203f7eec --- /dev/null +++ b/agentcore-web-search-cdk/package.json @@ -0,0 +1,17 @@ +{ + "name": "agentcore-web-search-cdk", + "version": "1.0.0", + "scripts": { + "build": "tsc", + "cdk": "cdk" + }, + "dependencies": { + "aws-cdk-lib": "2.260.0", + "constructs": "^10.4.2" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "typescript": "~5.7.0", + "aws-cdk": "2.1128.0" + } +} diff --git a/agentcore-web-search-cdk/src/handler/index.py b/agentcore-web-search-cdk/src/handler/index.py new file mode 100644 index 0000000000..1ac23386bb --- /dev/null +++ b/agentcore-web-search-cdk/src/handler/index.py @@ -0,0 +1,75 @@ +""" +AWS Lambda function that invokes an Amazon Bedrock AgentCore Gateway +with the Web Search Tool to answer questions using live web data. +""" + +import json +import os +import urllib.request + + +def handler(event, context): + """Invoke the Amazon Bedrock AgentCore Gateway web search tool via MCP protocol.""" + try: + gateway_id = os.environ["GATEWAY_ID"] + region = os.environ.get("AWS_REGION", "us-east-1") + endpoint = os.environ.get( + "GATEWAY_URL", + f"https://{gateway_id}.gateway.bedrock-agentcore.{region}.amazonaws.com/mcp", + ) + + import botocore.session + from botocore.auth import SigV4Auth + from botocore.awsrequest import AWSRequest + + session = botocore.session.get_session() + credentials = session.get_credentials().get_frozen_credentials() + + action = event.get("action", "search") + + if action == "list_tools": + mcp_request = {"jsonrpc": "2.0", "id": "1", "method": "tools/list", "params": {}} + else: + query = event.get("query", "What is Amazon Bedrock AgentCore?") + max_results = event.get("maxResults", 5) + tool_name = event.get("toolName", "web-search-target___WebSearch") + mcp_request = { + "jsonrpc": "2.0", + "id": "1", + "method": "tools/call", + "params": { + "name": tool_name, + "arguments": { + "query": query[:200], + "maxResults": min(max(int(max_results), 1), 25), + }, + }, + } + + payload = json.dumps(mcp_request).encode("utf-8") + + request = AWSRequest( + method="POST", + url=endpoint, + data=payload, + headers={"Content-Type": "application/json", "Accept": "application/json"}, + ) + SigV4Auth(credentials, "bedrock-agentcore", region).add_auth(request) + + req = urllib.request.Request( + endpoint, data=payload, headers=dict(request.headers), method="POST", + ) + + with urllib.request.urlopen(req, timeout=25) as resp: + response_body = json.loads(resp.read().decode("utf-8")) + + return { + "statusCode": 200, + "body": json.dumps({"results": response_body}), + } + + except Exception as e: + return { + "statusCode": 500, + "body": json.dumps({"error": str(e)}), + } diff --git a/agentcore-web-search-cdk/tsconfig.json b/agentcore-web-search-cdk/tsconfig.json new file mode 100644 index 0000000000..e7b20346a0 --- /dev/null +++ b/agentcore-web-search-cdk/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["es2022", "esnext.disposable"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": false, + "inlineSourceMap": true, + "inlineSources": true, + "experimentalDecorators": true, + "strictPropertyInitialization": false, + "outDir": "./build", + "rootDir": ".", + "types": ["node"], + "skipLibCheck": true + }, + "exclude": ["node_modules", "cdk.out"] +}