Hey devs π, if you saw my last post about building a minimal Model Context Protocol server with AWS Lambda using the Serverless Framework, this is the natural follow-up for those who prefer using AWS CDK.
The post on how to deploy an MCP server in a serverless environment was particularly well received and even got featured in two outstanding newsletters: Serverless Developer Advocate #33 by Lee Gilmore and Ready, Set, Cloud #160 by Allen Helton. I highly recommend subscribing to both as theyβre packed with insights and inspiration for serverless enthusiasts!
π€ Why Use CDK
AWS CDK
gives us fine-grained control over infrastructure, which makes it a great option if youβre scaling things up later.
Hereβs the repo if you want to jump in: π cdk-serverless-mcp-server
The best thing about AWS CDK is that lets you define infrastructure in actual code (TypeScript
, Python
, etc.), which can feel more natural as dev than writing YAML
or JSON
.
If you're:
- Planning to integrate with other AWS services
- Wanting more programmatic control
- Or just curious about how to do Infrastructure as Code with CDK without YAML/JSON files
β¦you should give it a try!
Here is a comparison table between Serverless Framework
, AWS SAM
and AWS CKD
. Read it carefully before choosing your preferred way to do IaC in your project.
Feature / Tool | Serverless Framework | AWS SAM (Serverless Application Model) | AWS CDK (Cloud Development Kit) |
---|---|---|---|
Ownership | V3 independent (deprecated), V4 enterprise, OSS alternative to V4 | AWS | AWS |
Abstraction Level | High-level | Medium-level | Low to medium-level |
Language | YAML + plugins (JavaScript/TS) | YAML + some scripting | TypeScript, Python, Java, C#, Go |
Cloud Provider Support | Multi-cloud (AWS, Azure, GCP, etc) | AWS only | AWS only |
Template Syntax | Custom syntax (serverless.yml) | CloudFormation-compatible YAML | Imperative (code-based) |
Local Development | Good support via plugins | Good (via sam local ) |
Limited, depends on constructs |
Deployment | CLI-driven | CLI-driven (sam deploy ) |
CLI-driven (cdk deploy ) |
State Management | Built-in via .serverless folder |
CloudFormation | CloudFormation |
Extensibility | High (plugins, hooks) | Moderate (some hooks/plugins) | High (custom constructs, reusable code) |
Maturity | Very mature | Mature | Rapidly growing |
Best For | Multi-cloud serverless apps | Simple AWS Lambda apps | Complex infrastructure-as-code on AWS |
Learning Curve | Low to moderate | Low | Moderate to high |
Testing/Debugging | Plugin-based | sam local invoke/start-api |
Manual / unit tests on code |
CI/CD Integration | Easy (via plugins or custom) | Easy (via CodePipeline or custom) | Easy (via CodePipeline or custom) |
Cost | V3 Free, V4 pricing | Free | Free |
π¦ Whatβs Inside the Repo
This project spins up as the previous one:
- An
AWS Lambda
function hosting a serverless MCP server - An
Amazon API Gateway
with aPOST
/mcp
route
Our goal is always to have a skeleton to deploy our MCP server in a serverless environment, but using AWS CDK
.
Project Structure
We add a bin
and lib
folder for our CDK app and stack.
Also note the cdk.json
to define our project.
cdk-serverless-mcp-server/
βββ __tests__/ # Jest tests
βββ bin/ # CDK entry point
βββ cdk-serverless-mcp-server.ts # CDK app
βββ lib/ # CDK stack
β βββ cdk-serverless-mcp-server-stack.ts # CDK stack
βββ src/ # Source code
β βββ index.mjs # MCP server handler
βββ .gitignore # Git ignore file
βββ cdk.json # CDK project config
βββ package.json # Project dependencies
βββ package-lock.json # Project lock file
βββ README.md # This documentation file
π Getting Started
To get it up and running follow those steps.
Install dependencies:
npm install
Install AWS CDK globally (if not already installed):
npm install -g aws-cdk
Test locally with jest
npm run test
ποΈ CDK code
You can easily read the code following comments in the stack file.
import { Stack, StackProps, Duration } from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigateway';
import {LayerVersion} from "aws-cdk-lib/aws-lambda";
export class CdkServerlessMcpServerStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props); // Initialize the stack
// Define runtime
const runtime = lambda.Runtime.NODEJS_22_X
// Create dependencies lambda layer
const dependenciesLayerName = 'dependencies-layer'; // Name of the layer
const dependenciesLayerFolder = 'layer/dependencies'; // Folder containing the layer code
const dependenciesLayerDesc = 'Layer containing project dependencies'; // Description of the layer
const dependenciesLayerProps = {
code: lambda.Code.fromAsset(dependenciesLayerFolder), // Path to the layer code
compatibleRuntimes: [ runtime], // Specify the compatible runtimes
description: dependenciesLayerDesc, // Description of the layer
};
const dependenciesLayer = new LayerVersion(this, dependenciesLayerName, dependenciesLayerProps);
// Create lambda function
const mcpLambda = new lambda.Function(this, 'McpHandler', {
runtime: runtime, // The runtime environment for the Lambda function
handler: 'index.handler', // The name of the exported function in our code
code: lambda.Code.fromAsset('src'), // Path to our lambda function code
timeout: Duration.seconds(29), // Set timeout to 29 seconds
layers: [dependenciesLayer] // Add the layer to the lambda function
});
// Create API Gateway
const api = new apigateway.RestApi(this, 'McpApi', {
restApiName: 'MCP Service', // The name of the API
});
// Add a resource and method to the API Gateway
const mcpResource = api.root.addResource('mcp'); // Create a resource named 'mcp'
mcpResource.addMethod('POST', new apigateway.LambdaIntegration(mcpLambda)); // Add a POST method to the resource
}
}
Whatβs happening here?
-
Node.js 22
support: just set the runtime toNODEJS_22_X
and you're good to go. - Custom
Lambda Layer
: to avoid bloating your function zip and to improve reusability, we package our dependencies into a Lambda Layer (layer/dependencies
). This also helps with cold starts! - A
Lambda
function withMCP server
code -
API Gateway
integration: we expose the Lambda via API Gateway, setting up aPOST
method on the/mcp
endpoint in just a few lines.
π‘ Deploy to AWS
Follow those steps.
Before all, install dependencies for the layer
npm run layer-dependencies-install
You should bootstrap the CDK (just one time on the account):
cdk bootstrap
Then, deploy the stack:
cdk deploy
After deployment, the MCP server will be live at the URL output by the command.
Here is how it looks like when deploying:
Take a moment to copy the endpoint shown after running the command.
π§ͺ Once deployed, test with curl requests
List tools
Change your-endpoint
with the one noted.
curl --location 'https://your-endpoint/dev/mcp' \
--header 'content-type: application/json' \
--header 'accept: application/json' \
--header 'jsonrpc: 2.0' \
--data '{
"jsonrpc": "2.0",
"method": "tools/list",
"id": 1
}'
Here is an example (response linted with jq):
β Use the add Tool
Change your-endpoint
with the one noted.
curl --location 'https://your-endpoint/dev/mcp' \
--header 'content-type: application/json' \
--header 'accept: application/json' \
--header 'jsonrpc: 2.0' \
--data '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "add",
"arguments": {
"a": 5,
"b": 3
}
}
}'
Here is an example (response linted with jq):
βοΈ Next Step
I'm planning to continue this series giving an example with SAM! Let me know if this is needed by you in the comments!
π Who am I
I'm D. De Sio and I work as a Head of Software Engineering in Eleva.
I'm currently (Apr 2025) an AWS Certified Solution Architect Professional and AWS Certified DevOps Engineer Professional, but also a User Group Leader (in Pavia), an AWS Community Builder and, last but not least, a #serverless enthusiast.
My work in this field is to advocate about serverless and help as more dev teams to adopt it, as well as customers break their monolith into API and micro-services using it.
Top comments (0)