Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lambda-mcp-server-bedrock-cdk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules
build
cdk.out
*.js
*.d.ts
2 changes: 2 additions & 0 deletions lambda-mcp-server-bedrock-cdk/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
build
cdk.out
102 changes: 102 additions & 0 deletions lambda-mcp-server-bedrock-cdk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Lambda MCP Server with Amazon Bedrock

This pattern deploys a stateless [MCP (Model Context Protocol)](https://modelcontextprotocol.io/) server on AWS Lambda with a Function URL. The server exposes Bedrock-powered tools that any MCP client can use.

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/lambda-mcp-server-bedrock-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.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Node and NPM](https://nodejs.org/en/download/) installed
* [AWS CDK](https://docs.aws.amazon.com/cdk/latest/guide/cli.html) installed

## How it works

MCP (Model Context Protocol) is an open standard for connecting AI assistants to external tools and data sources. This pattern deploys a serverless MCP server that:

- **Runs on Lambda** with a Function URL (IAM auth) — no API Gateway needed
- **Implements MCP protocol** (JSON-RPC 2.0 over HTTP) with `initialize`, `tools/list`, and `tools/call`
- **Exposes two tools**: `ask_bedrock` (general Q&A) and `summarize` (text summarization)
- **Stateless design** — scales horizontally with no session management

```
MCP Client (Claude Desktop / Kiro / VS Code)
↓ HTTP POST (JSON-RPC)
Lambda Function URL
↓ tools/call
Amazon Bedrock (Claude) → Response
```

## Available MCP Tools

| Tool | Description | Input |
|------|-------------|-------|
| `ask_bedrock` | Ask Bedrock a question | `prompt` (string), `max_tokens` (number, optional) |
| `summarize` | Summarize text | `text` (string) |

## Deployment Instructions

1. Clone the repository and navigate to the pattern directory:
```bash
git clone https://github.com/aws-samples/serverless-patterns
cd serverless-patterns/lambda-mcp-server-bedrock-cdk
```

2. Install dependencies:
```bash
npm install
```

3. Deploy the stack:
```bash
cdk deploy
```

## Testing

Test the MCP server directly with curl (requires [AWS SigV4 signing](https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html)):

```bash
# Initialize (using awscurl for SigV4)
awscurl --service lambda -X POST <McpServerUrl> \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}}}'

# List tools
awscurl --service lambda -X POST <McpServerUrl> \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'

# Call ask_bedrock tool
awscurl --service lambda -X POST <McpServerUrl> \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"ask_bedrock","arguments":{"prompt":"What is MCP?"}}}'
```

## Connecting MCP Clients

Add to your MCP client configuration (e.g., Claude Desktop `claude_desktop_config.json`):

```json
{
"mcpServers": {
"lambda-bedrock": {
"url": "<McpEndpoint>"
}
}
}
```

## Cleanup

```bash
cdk destroy
```

----
Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
6 changes: 6 additions & 0 deletions lambda-mcp-server-bedrock-cdk/bin/app.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { LambdaMcpServerBedrockStack } from '../lib/lambda-mcp-server-bedrock-stack';

const app = new cdk.App();
new LambdaMcpServerBedrockStack(app, 'LambdaMcpServerBedrockStack');
12 changes: 12 additions & 0 deletions lambda-mcp-server-bedrock-cdk/cdk.context.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"acknowledged-issue-numbers": [
33623,
34635,
34892,
32775,
33623,
34635,
34892,
32775
]
}
3 changes: 3 additions & 0 deletions lambda-mcp-server-bedrock-cdk/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"app": "npx ts-node bin/app.ts"
}
38 changes: 38 additions & 0 deletions lambda-mcp-server-bedrock-cdk/example-pattern.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"title": "Lambda MCP Server with Amazon Bedrock",
"description": "Deploy a stateless MCP (Model Context Protocol) server on Lambda with Function URL that exposes Bedrock tools",
"language": "TypeScript",
"level": "300",
"framework": "CDK",
"introBox": {
"headline": "How it works",
"text": [
"This pattern deploys a stateless MCP server on AWS Lambda with a Function URL endpoint.",
"The server implements the MCP Streamable HTTP transport protocol and exposes two Bedrock-powered tools: ask_bedrock and summarize.",
"Any MCP client (Claude Desktop, Kiro, VS Code extensions) can connect to the Function URL and use the tools to interact with Amazon Bedrock."
]
},
"gitHub": {
"template": "https://github.com/aws-samples/serverless-patterns/tree/main/lambda-mcp-server-bedrock-cdk",
"templateURL": "serverless-patterns/lambda-mcp-server-bedrock-cdk"
},
"resources": {
"bullets": [
{ "text": "MCP Specification - Streamable HTTP Transport", "link": "https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http" },
{ "text": "Sample Serverless MCP Servers", "link": "https://github.com/aws-samples/sample-serverless-mcp-servers" }
]
},
"deploy": {
"text": ["cdk deploy"]
},
"cleanup": {
"text": ["cdk destroy"]
},
"authors": [
{
"name": "Nithin Chandran R",
"bio": "Technical Account Manager at AWS",
"linkedin": "nithin-chandran-r"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Construct } from 'constructs';

export class LambdaMcpServerBedrockStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const modelId = new cdk.CfnParameter(this, 'BedrockModelId', {
type: 'String',
default: 'us.anthropic.claude-sonnet-4-20250514-v1:0',
description: 'Bedrock model ID (inference profile)',
});

// MCP Server Lambda with Function URL (Streamable HTTP transport)
const mcpFn = new lambda.Function(this, 'McpServerFunction', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('src'),
timeout: cdk.Duration.seconds(60),
memorySize: 256,
environment: {
MODEL_ID: modelId.valueAsString,
},
});

mcpFn.addToRolePolicy(new iam.PolicyStatement({
actions: ['bedrock:InvokeModel'],
resources: ['*'],
}));

// Function URL for MCP Streamable HTTP transport
const fnUrl = mcpFn.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.AWS_IAM,
invokeMode: lambda.InvokeMode.BUFFERED,
});

new cdk.CfnOutput(this, 'McpServerUrl', { value: fnUrl.url });
new cdk.CfnOutput(this, 'McpEndpoint', {
value: cdk.Fn.join('', [fnUrl.url, 'mcp']),
description: 'MCP endpoint URL for client configuration',
});
}
}
21 changes: 21 additions & 0 deletions lambda-mcp-server-bedrock-cdk/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "lambda-mcp-server-bedrock-cdk",
"version": "0.1.0",
"bin": {
"lambda-mcp-server-bedrock-cdk": "bin/app.js"
},
"scripts": {
"build": "tsc",
"cdk": "cdk"
},
"devDependencies": {
"@types/node": "22.7.9",
"aws-cdk": "2.1003.0",
"ts-node": "^10.9.2",
"typescript": "~5.6.3"
},
"dependencies": {
"aws-cdk-lib": "2.189.1",
"constructs": "^10.0.0"
}
}
115 changes: 115 additions & 0 deletions lambda-mcp-server-bedrock-cdk/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');

const client = new BedrockRuntimeClient();

// Stateless MCP Server on Lambda — implements MCP Streamable HTTP transport
// Supports: tools/list, tools/call, initialize
exports.handler = async (event) => {
const method = event.requestContext?.http?.method || 'GET';
const path = event.rawPath || '/';

// Health check
if (method === 'GET' && path === '/') {
return { statusCode: 200, body: JSON.stringify({ status: 'ok', server: 'lambda-mcp-bedrock' }) };
}

// MCP endpoint
if (method === 'POST') {
const body = JSON.parse(event.body || '{}');
const response = await handleMcpRequest(body);
return {
statusCode: 200,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(response),
};
}

return { statusCode: 405, body: 'Method not allowed' };
};

async function handleMcpRequest(request) {
const { method, id, params } = request;

if (method === 'initialize') {
return {
jsonrpc: '2.0', id,
result: {
protocolVersion: '2025-03-26',
capabilities: { tools: { listChanged: false } },
serverInfo: { name: 'lambda-bedrock-mcp', version: '1.0.0' },
},
};
}

if (method === 'tools/list') {
return {
jsonrpc: '2.0', id,
result: {
tools: [
{
name: 'ask_bedrock',
description: 'Ask Amazon Bedrock (Claude) a question and get an AI-generated response',
inputSchema: {
type: 'object',
properties: {
prompt: { type: 'string', description: 'The question or prompt to send to Bedrock' },
max_tokens: { type: 'number', description: 'Maximum tokens in response', default: 512 },
},
required: ['prompt'],
},
},
{
name: 'summarize',
description: 'Summarize text using Amazon Bedrock (Claude)',
inputSchema: {
type: 'object',
properties: {
text: { type: 'string', description: 'The text to summarize' },
},
required: ['text'],
},
},
],
},
};
}

if (method === 'tools/call') {
const toolName = params?.name;
const args = params?.arguments || {};

if (toolName === 'ask_bedrock') {
const answer = await invokeBedrock(args.prompt, args.max_tokens || 512);
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: answer }] } };
}

if (toolName === 'summarize') {
const summary = await invokeBedrock(`Summarize the following text concisely:\n\n${args.text}`, 512);
return { jsonrpc: '2.0', id, result: { content: [{ type: 'text', text: summary }] } };
}

return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${toolName}` } };
}

// notifications/initialized — acknowledge
if (method === 'notifications/initialized') {
return null;
}

return { jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown method: ${method}` } };
}

async function invokeBedrock(prompt, maxTokens) {
const response = await client.send(new InvokeModelCommand({
modelId: process.env.MODEL_ID,
contentType: 'application/json',
accept: 'application/json',
body: JSON.stringify({
anthropic_version: 'bedrock-2023-05-31',
max_tokens: maxTokens,
messages: [{ role: 'user', content: prompt }],
}),
}));
const body = JSON.parse(new TextDecoder().decode(response.body));
return body.content[0].text;
}
24 changes: 24 additions & 0 deletions lambda-mcp-server-bedrock-cdk/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": ["es2020"],
"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": "."
},
"exclude": ["node_modules", "build"]
}