diff --git a/agentcore-gateway-lambda-cdk/README.md b/agentcore-gateway-lambda-cdk/README.md new file mode 100644 index 000000000..7f809793e --- /dev/null +++ b/agentcore-gateway-lambda-cdk/README.md @@ -0,0 +1,117 @@ +# Amazon Bedrock AgentCore Gateway with Lambda tools + +This pattern deploys an Amazon Bedrock AgentCore Gateway that exposes Lambda functions as MCP (Model Context Protocol) tools. Any MCP-compatible client can discover and invoke the tools via the Gateway's MCP endpoint. + +Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/agentcore-gateway-lambda-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 +* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) +* [Node.js 22+](https://nodejs.org/en/download/) installed +* [AWS CDK v2](https://docs.aws.amazon.com/cdk/v2/guide/getting-started.html) installed +* [Python 3.9+](https://www.python.org/downloads/) with `boto3` installed (for testing) + +## Architecture + +``` +┌──────────────┐ ┌──────────────────────────┐ ┌──────────────────┐ +│ MCP Client │────▶│ AgentCore Gateway │────▶│ Lambda Function │ +│ (Agent/CLI) │ │ (MCP protocol, IAM auth) │ │ (Tool handler) │ +└──────────────┘ └──────────────────────────┘ └──────────────────┘ + │ tools/list │ │ get_weather │ + │ tools/call │ │ get_time │ + └──────────────────────────┘ └──────────────────┘ +``` + +## How it works + +1. The AgentCore Gateway is created with MCP protocol and IAM authentication. +2. A Lambda function is registered as a Gateway target with inline tool definitions (get_weather, get_time). +3. MCP clients send `tools/list` to discover available tools and `tools/call` to invoke them. +4. The Gateway routes tool calls to the Lambda function, passing the tool arguments. The Lambda returns MCP-formatted content responses. + +## Deployment Instructions + +1. Clone the repository: + ```bash + git clone https://github.com/aws-samples/serverless-patterns + cd serverless-patterns/agentcore-gateway-lambda-cdk + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Deploy the stack: + ```bash + cdk deploy + ``` + +4. Note the outputs printed after deployment. You will need `GatewayUrl` for testing. + Example output: + ``` + Outputs: + AgentcoreGatewayLambdaStack.GatewayId = tool-gateway-bdastack-mkrkf8fntt + AgentcoreGatewayLambdaStack.GatewayUrl = https://tool-gateway-bdastack-mkrkf8fntt.gateway.bedrock-agentcore.us-east-1.amazonaws.com/mcp + AgentcoreGatewayLambdaStack.TargetId = KNBRZ24GFJ + AgentcoreGatewayLambdaStack.FunctionName = AgentcoreGatewayLambdaStack-ToolFn-AbCdEfGh + ``` + +## Testing + +The Gateway uses IAM authentication, so requests must be signed with SigV4. Replace `` with the `GatewayUrl` value from the deploy output, and `` with your deployment region (e.g. `us-east-1`). + +1. List available tools: + ```python + import boto3, json, urllib.request + from botocore.auth import SigV4Auth + from botocore.awsrequest import AWSRequest + + session = boto3.Session() + creds = session.get_credentials().get_frozen_credentials() + url = "" + region = "" + + payload = json.dumps({"jsonrpc": "2.0", "method": "tools/list", "id": 1}) + req = AWSRequest(method="POST", url=url, data=payload, + headers={"Content-Type": "application/json"}) + SigV4Auth(creds, "bedrock-agentcore", region).add_auth(req) + + http_req = urllib.request.Request(url, data=payload.encode(), + headers=dict(req.headers), method="POST") + resp = urllib.request.urlopen(http_req, timeout=30) + print(json.dumps(json.loads(resp.read()), indent=2)) + ``` + +2. Call a tool: + ```python + payload = json.dumps({ + "jsonrpc": "2.0", "method": "tools/call", "id": 2, + "params": {"name": "city-tools___get_weather", + "arguments": {"city": "Tokyo"}} + }) + req = AWSRequest(method="POST", url=url, data=payload, + headers={"Content-Type": "application/json"}) + SigV4Auth(creds, "bedrock-agentcore", region).add_auth(req) + + http_req = urllib.request.Request(url, data=payload.encode(), + headers=dict(req.headers), method="POST") + resp = urllib.request.urlopen(http_req, timeout=30) + print(json.dumps(json.loads(resp.read()), indent=2)) + ``` + +## Cleanup + +```bash +cdk destroy +``` + +---- +Copyright 2026 Amazon.com, Inc. or its affiliates. All Rights Reserved. + +SPDX-License-Identifier: MIT-0 diff --git a/agentcore-gateway-lambda-cdk/bin/app.ts b/agentcore-gateway-lambda-cdk/bin/app.ts new file mode 100644 index 000000000..1d00943ea --- /dev/null +++ b/agentcore-gateway-lambda-cdk/bin/app.ts @@ -0,0 +1,12 @@ +#!/usr/bin/env node +import "source-map-support/register"; +import * as cdk from "aws-cdk-lib"; +import { AgentcoreGatewayLambdaStack } from "../lib/agentcore-gateway-lambda-stack"; + +const app = new cdk.App(); +new AgentcoreGatewayLambdaStack(app, "AgentcoreGatewayLambdaStack", { + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, +}); diff --git a/agentcore-gateway-lambda-cdk/cdk.json b/agentcore-gateway-lambda-cdk/cdk.json new file mode 100644 index 000000000..a6700a2ff --- /dev/null +++ b/agentcore-gateway-lambda-cdk/cdk.json @@ -0,0 +1,3 @@ +{ + "app": "npx ts-node --prefer-ts-exts bin/app.ts" +} diff --git a/agentcore-gateway-lambda-cdk/example-pattern.json b/agentcore-gateway-lambda-cdk/example-pattern.json new file mode 100644 index 000000000..c67ef5002 --- /dev/null +++ b/agentcore-gateway-lambda-cdk/example-pattern.json @@ -0,0 +1,50 @@ +{ + "title": "Amazon Bedrock AgentCore Gateway with Lambda tools", + "description": "Deploy an AgentCore Gateway with Lambda tool targets, exposing tools via the MCP protocol with IAM authentication.", + "language": "TypeScript", + "level": "300", + "framework": "AWS CDK", + "services": { + "from": "bedrockagentcore", + "to": "lambda" + }, + "introBox": { + "headline": "How it works", + "text": [ + "This pattern deploys an Amazon Bedrock AgentCore Gateway that exposes Lambda functions as MCP (Model Context Protocol) tools. The Gateway handles tool discovery (tools/list) and invocation (tools/call) with IAM-based authentication.", + "AgentCore Gateway provides a unified connectivity layer between AI agents and tools. Lambda functions are registered as targets with inline tool schemas, enabling any MCP-compatible client to discover and invoke them." + ] + }, + "gitHub": { + "template": { + "repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/agentcore-gateway-lambda-cdk", + "templateURL": "serverless-patterns/agentcore-gateway-lambda-cdk", + "projectFolder": "agentcore-gateway-lambda-cdk", + "templateFile": "lib/agentcore-gateway-lambda-stack.ts" + } + }, + "resources": { + "bullets": [ + { "text": "Amazon Bedrock AgentCore Gateway", "link": "https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-building.html" }, + { "text": "Model Context Protocol (MCP)", "link": "https://modelcontextprotocol.io/" }, + { "text": "Run custom MCP proxies on AgentCore Runtime", "link": "https://aws.amazon.com/blogs/machine-learning/run-custom-mcp-proxies-serverless-on-amazon-bedrock-agentcore-runtime/" } + ] + }, + "deploy": { + "text": ["cdk deploy"], + "file": "lib/agentcore-gateway-lambda-stack.ts" + }, + "testing": { + "text": ["See the README for testing instructions."] + }, + "cleanup": { + "text": ["cdk destroy"] + }, + "authors": [ + { + "name": "Nithin Chandran R", + "bio": "Technical Account Manager at AWS", + "linkedin": "nithin-chandran-r" + } + ] +} diff --git a/agentcore-gateway-lambda-cdk/lib/agentcore-gateway-lambda-stack.ts b/agentcore-gateway-lambda-cdk/lib/agentcore-gateway-lambda-stack.ts new file mode 100644 index 000000000..bd93a351d --- /dev/null +++ b/agentcore-gateway-lambda-cdk/lib/agentcore-gateway-lambda-stack.ts @@ -0,0 +1,107 @@ +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 { Construct } from "constructs"; + +export class AgentcoreGatewayLambdaStack extends cdk.Stack { + constructor(scope: Construct, id: string, props?: cdk.StackProps) { + super(scope, id, props); + + // Lambda tool handler + const toolFn = new lambda.Function(this, "ToolFn", { + runtime: lambda.Runtime.NODEJS_22_X, + handler: "index.handler", + code: lambda.Code.fromAsset("src/tool-lambda"), + timeout: cdk.Duration.seconds(30), + memorySize: 256, + description: "AgentCore Gateway Lambda tool target", + }); + + // Gateway IAM role with confused-deputy protection + const gatewayRole = new iam.Role(this, "GatewayRole", { + assumedBy: new iam.ServicePrincipal("bedrock-agentcore.amazonaws.com", { + conditions: { + StringEquals: { + "aws:SourceAccount": this.account, + }, + }, + }), + description: "Role for AgentCore Gateway to invoke Lambda tools", + }); + toolFn.grantInvoke(gatewayRole); + + // AgentCore Gateway (MCP protocol, IAM auth) + const gateway = new agentcore.CfnGateway(this, "Gateway", { + name: `tool-gateway-${cdk.Names.uniqueId(this).slice(-8).toLowerCase()}`, + protocolType: "MCP", + authorizerType: "AWS_IAM", + roleArn: gatewayRole.roleArn, + description: "MCP Gateway exposing Lambda tools", + }); + + // Gateway target — Lambda with inline tool definitions + const target = new agentcore.CfnGatewayTarget(this, "ToolTarget", { + gatewayIdentifier: gateway.attrGatewayIdentifier, + name: "city-tools", + description: "Weather and time tools backed by Lambda", + credentialProviderConfigurations: [ + { + credentialProviderType: "GATEWAY_IAM_ROLE", + }, + ], + targetConfiguration: { + mcp: { + lambda: { + lambdaArn: toolFn.functionArn, + toolSchema: { + inlinePayload: [ + { + name: "get_weather", + description: "Get current weather for a city", + inputSchema: { + type: "object", + properties: { + city: { + type: "string", + description: "City name (e.g. Tokyo, London)", + }, + }, + required: ["city"], + }, + }, + { + name: "get_time", + description: "Get current UTC time for a city", + inputSchema: { + type: "object", + properties: { + city: { + type: "string", + description: "City name", + }, + }, + required: ["city"], + }, + }, + ], + }, + }, + }, + }, + }); + + new cdk.CfnOutput(this, "GatewayId", { + value: gateway.attrGatewayIdentifier, + }); + new cdk.CfnOutput(this, "GatewayUrl", { + value: gateway.attrGatewayUrl, + }); + new cdk.CfnOutput(this, "TargetId", { + value: target.attrTargetId, + }); + new cdk.CfnOutput(this, "FunctionName", { + value: toolFn.functionName, + }); + } +} diff --git a/agentcore-gateway-lambda-cdk/package.json b/agentcore-gateway-lambda-cdk/package.json new file mode 100644 index 000000000..057048dac --- /dev/null +++ b/agentcore-gateway-lambda-cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "agentcore-gateway-lambda-cdk", + "version": "1.0.0", + "bin": { "app": "bin/app.ts" }, + "scripts": { "build": "tsc", "cdk": "cdk" }, + "dependencies": { + "aws-cdk-lib": "^2.180.0", + "constructs": "^10.4.2" + }, + "devDependencies": { + "@types/node": "^22.0.0", + "ts-node": "^10.9.0", + "typescript": "~5.7.0" + } +} diff --git a/agentcore-gateway-lambda-cdk/src/tool-lambda/index.js b/agentcore-gateway-lambda-cdk/src/tool-lambda/index.js new file mode 100644 index 000000000..3aea01934 --- /dev/null +++ b/agentcore-gateway-lambda-cdk/src/tool-lambda/index.js @@ -0,0 +1,29 @@ +// AgentCore Gateway Lambda tool target handler +// The Gateway routes MCP tool calls to this Lambda, passing only the arguments. +exports.handler = async (event) => { + const city = typeof event.city === "string" + ? event.city.slice(0, 100).replace(/[^\w\s-]/g, "") + : ""; + + if (!city) { + return { + content: [{ type: "text", text: JSON.stringify({ error: "city is required" }) }], + isError: true, + }; + } + + return { + content: [ + { + type: "text", + text: JSON.stringify({ + city, + temperature: `${Math.floor(Math.random() * 30 + 5)}°C`, + condition: ["Sunny", "Cloudy", "Rainy", "Windy"][Math.floor(Math.random() * 4)], + humidity: `${Math.floor(Math.random() * 60 + 30)}%`, + utcTime: new Date().toISOString(), + }), + }, + ], + }; +}; diff --git a/agentcore-gateway-lambda-cdk/tsconfig.json b/agentcore-gateway-lambda-cdk/tsconfig.json new file mode 100644 index 000000000..3628e7ff6 --- /dev/null +++ b/agentcore-gateway-lambda-cdk/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["es2020"], + "declaration": true, + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "noImplicitThis": true, + "alwaysStrict": true, + "esModuleInterop": true, + "outDir": "build", + "rootDir": "." + }, + "exclude": ["node_modules", "cdk.out"] +}