# Create a new directory for our project
mkdir lambda-mcp-server
cd lambda-mcp-server
# Initialize a new CDK project
cdk init app --language typescript
# Install necessary dependencies
npm install @aws-cdk/aws-lambda @aws-cdk/aws-apigateway @aws-cdk/aws-logs @modelcontextprotocol/sdk middy zod
# Create .env file
cp .env.template .env
CDK_ACCOUNT=123456789012
CDK_REGION=us-west-2
STACK_ENDPOINT=https://stack.nocodo.ai/execute/[org-id]/[project-id]
/**
* AWS CDK Stack Definition for Lambda MCP Server
*
* This file defines the AWS infrastructure for our MCP server using the AWS CDK.
* It creates:
* - An API Gateway REST API endpoint
* - A Lambda function for API key authorization
* - A Lambda function for handling MCP protocol requests
* - Log retention configuration for both Lambda functions
*/
// Import CDK core constructs
import { Stack, StackProps, CfnOutput, Duration } from "aws-cdk-lib";
import { Construct } from "constructs";
// Import Lambda-specific constructs
import { Runtime } from "aws-cdk-lib/aws-lambda";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs";
// Import API Gateway constructs
import {
RestApi,
LambdaIntegration,
AuthorizationType,
TokenAuthorizer,
EndpointType,
} from "aws-cdk-lib/aws-apigateway";
// Node.js path utilities for file references
import * as path from "path";
// Import constant for environment variable names
import { API_KEY, STACK_ENDPOINT } from "../src/const/environment.const";
// Import CloudWatch Logs constructs for log retention
import { RetentionDays } from "aws-cdk-lib/aws-logs";
// Import API Gateway V2 behaviors (used in integration configuration)
import { PassthroughBehavior } from "aws-cdk-lib/aws-apigatewayv2";
export interface LambdaMcpStackProps extends StackProps {
stackEndpoint: string;
apiKey: string;
}
/**
* Main CDK Stack for the Lambda MCP Server
*
* This stack defines all AWS resources needed to run an MCP server on AWS Lambda.
*/
export class LambdaMcpStack extends Stack {
constructor(scope: Construct, id: string, props: LambdaMcpStackProps) {
super(scope, id, props);
/**
* Step 1: Create the API Key Authorizer Lambda
*
* This Lambda function validates API keys sent in request headers.
* It will check if the x-api-key header matches our configured value.
*/
const authorizerLambda = new NodejsFunction(this, "ApiKeyAuthorizer", {
runtime: Runtime.NODEJS_22_X, // Using Node.js 22.x runtime
handler: "handler", // Function exported as 'handler'
entry: path.join(__dirname, "../src/authorizer/api-key-authorizer.ts"), // Path to source code
environment: {
// Setting the API key as an environment variable
// In a production environment, you would use AWS Secrets Manager instead
[API_KEY]: props.apiKey,
},
logRetention: RetentionDays.ONE_WEEK, // Keep logs for one week
});
/**
* Step 2: Create the API Gateway Token Authorizer
*
* This connects our authorizer Lambda to API Gateway.
* It implements a token authorizer that checks the x-api-key header.
*/
const authorizer = new TokenAuthorizer(this, "ApiKeyTokenAuthorizer", {
handler: authorizerLambda,
identitySource: "method.request.header.x-api-key", // Extract the API key from this header
resultsCacheTtl: Duration.seconds(10), // Cache authorization results for 10 seconds to reduce Lambda invocations
});
/**
* Step 3: Create the main MCP Lambda handler
*
* This Lambda function processes MCP protocol requests and provides tool functionality.
* It uses the @modelcontextprotocol/sdk library to implement the MCP server.
*/
const mcpLambda = new NodejsFunction(this, "McpHandler", {
runtime: Runtime.NODEJS_22_X, // Using Node.js 22.x runtime
handler: "handler", // Function exported as 'handler'
entry: path.join(__dirname, "../src/handler/mcp-handler.ts"), // Path to source code
logRetention: RetentionDays.ONE_WEEK, // Keep logs for one week
environment: {
// Setting the stack endpoint as an environment variable
// This is the URL of the external service we will call
[STACK_ENDPOINT]: props.stackEndpoint,
},
timeout: Duration.minutes(3), // Set a timeout for the Lambda function (3 minutes)
});
/**
* Step 4: Create the REST API Gateway
*
* This creates the API Gateway REST API that will expose our Lambda function.
* REST APIs support longer timeouts than HTTP APIs, allowing us to set a 3-minute timeout.
*/
const restApi = new RestApi(this, "McpRestApi", {
restApiName: "mcp-api", // Name of the API in AWS
description: "API for MCP integration", // Description for the API
endpointTypes: [EndpointType.REGIONAL], // Use regional endpoints to avoid 30s timeout with edge endpoints
});
/**
* Step 5: Add the MCP route with authorizer
*
* This configures the API to:
* - Accept POST requests at the /mcp endpoint
* - Integrate with our MCP Lambda function
* - Require authorization via the API key authorizer
*/
const mcpResource = restApi.root.addResource("mcp");
mcpResource.addMethod(
"POST", // MCP protocol uses POST requests
new LambdaIntegration(mcpLambda, {
timeout: Duration.minutes(3), // Set integration timeout to 3 minutes
proxy: true, // Proxy integration to pass through request and response
passthroughBehavior: PassthroughBehavior.WHEN_NO_TEMPLATES, // Pass through requests without templates
}), // Connect to our Lambda
{
authorizer, // Use our API key authorizer
authorizationType: AuthorizationType.CUSTOM, // Specify that we're using a custom authorizer
operationName: "processMcpRequest",
}
);
/**
* Step 6: Output the API URL
*
* This creates a CloudFormation output that shows the API URL after deployment.
* You'll need this URL to configure VS Code to use your MCP server.
*/
new CfnOutput(this, "ApiUrl", {
value: restApi.url + "mcp", // Include the resource path in the URL
description: "URL of the REST API",
});
}
}
/**
* API Key Authorizer Lambda Function
*
* This Lambda function acts as a custom request authorizer for API Gateway.
* It validates requests by checking if they include the correct API key
* in the 'x-api-key' header and returns an IAM policy document.
*/
// Import AWS Lambda authorizer types
import {
APIGatewayAuthorizerResult,
StatementEffect,
APIGatewayTokenAuthorizerEvent,
} from "aws-lambda";
// Import our environment variable constant
import { API_KEY } from "../const/environment.const";
/**
* Lambda handler function for API key authorization
*
* This function:
* 1. Receives an API Gateway request event
* 2. Extracts the API key from the request headers
* 3. Compares it to the expected API key from environment variables
* 4. Returns an IAM policy that allows or denies access
*
* @param event - The API Gateway request event containing headers
* @param context - The Lambda context object
* @param callback - Callback function to return the authorization result
*/
export const handler = (
event: APIGatewayTokenAuthorizerEvent,
context: any,
callback: (error: any, policy?: APIGatewayAuthorizerResult) => void
): void => {
console.log("API Key Authorizer invoked:", JSON.stringify(event, null, 2));
// Get the API key from environment variables (set in the CDK stack)
const expectedApiKey: string = process.env[API_KEY]!;
// Extract request parameters
const apiKey = event.authorizationToken;
// Check if the API key is present and matches the expected value
if (apiKey === expectedApiKey) {
console.log("Authorization successful");
callback(null, generateAllow("user", event.methodArn));
} else {
console.log("Authorization failed");
callback("Unauthorized");
}
};
/**
* Helper function to generate an IAM policy
*
* @param principalId - The principal ID (user identifier)
* @param effect - The effect of the policy (Allow/Deny)
* @param resource - The resource ARN to apply the policy to
* @returns The authorization response with policy document
*/
const generatePolicy = (
principalId: string,
effect: StatementEffect,
resource: string
): APIGatewayAuthorizerResult => {
if (!effect || !resource) {
throw new Error("Effect and resource are required");
}
let authResponse: APIGatewayAuthorizerResult = {
principalId: principalId,
policyDocument: {
Version: "2012-10-17",
Statement: [
{
Action: "execute-api:Invoke",
Effect: effect,
Resource: resource,
},
],
},
};
// Optional context values that can be accessed in the integration
authResponse.context = {
apiKeyValid: true,
timestamp: new Date().toISOString(),
};
return authResponse;
};
/**
* Generate an Allow policy
*/
const generateAllow = (
principalId: string,
resource: string
): APIGatewayAuthorizerResult => {
return generatePolicy(principalId, "Allow", resource);
};
/**
* Generate a Deny policy
*/
const generateDeny = (
principalId: string,
resource: string
): APIGatewayAuthorizerResult => {
return generatePolicy(principalId, "Deny", resource);
};
/**
* Environment variable constants
*
* These constants define the names of environment variables used throughout the project
*/
export const API_KEY = "API_KEY";
export const STACK_ENDPOINT = "STACK_ENDPOINT";
/**
* MCP Server Lambda Handler
*
* This file defines the main Lambda function that handles MCP protocol requests.
* It creates an MCP server with tools and configures middleware for HTTP integration.
*/
// Import the Middy middleware framework for Lambda functions
import middy from "@middy/core";
// Import error handler middleware to process HTTP errors
import httpErrorHandler from "@middy/http-error-handler";
// Import the Model Context Protocol server class
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// Import Zod for schema validation
import { z } from "zod";
// Import the MCP middleware for Middy that bridges HTTP requests to MCP server
import mcpMiddleware from "../mcp/index";
// Import environment variable constants
import { STACK_ENDPOINT } from "../const/environment.const";
/**
* Step 1: Create an MCP server instance
*
* This defines our server with basic metadata about its name and version.
* The MCP server will process protocol-compliant requests and route them to the appropriate tools.
*/
const server = new McpServer({
name: "My Lambda hosted MCP Server",
version: "1.0.0",
});
/**
* Step 2: Register tools with the MCP server
*
* We define a specialized "code-review" tool that:
* - Helps AI models perform code reviews on small snippets
* - Analyzes code for bugs, code smells, and improvement opportunities
* - Is optimized for 5-50 line snippets
*
* This tool sends the code to an external API service for analysis:
* - Accepts a code string as input
* - Sends the code to a hosted code review service
* - Returns the analysis results from the external service
*/
server.tool(
// Tool name - identifies this capability to the AI model
"code-review",
// Tool description - helps the AI understand when and how to use this tool
"The Code Review Assistant is an MCP tool that enables an LLM to perform static code reviews on small, self-contained code snippets. It works best with individual functions, classes, or short scripts. Users input a code snippet in plain text, and the tool analyzes it for bugs, code smells, and improvement opportunities. It provides suggestions on readability, performance, maintainability, and adherence to best practices. Ideal use cases include peer review automation, onboarding support, and code refactoring. It's most effective on focused code sections between 5–50 lines. The tool does not execute code and is not suited for large or interdependent codebases.",
// Parameter schema using Zod for validation
// Accepts a string parameter named 'code' containing the code to be reviewed
{ code: z.string() },
// Tool implementation
// Uses external API to perform code review
async ({ code }) => {
try {
// Call the external code review service
const endpoint = process.env[STACK_ENDPOINT];
if (!endpoint) {
throw new Error("STACK_ENDPOINT environment variable is not defined");
}
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ code }),
});
// Check if the request was successful
if (!response.ok) {
throw new Error(
`Code review service responded with status: ${response.status}`
);
}
// Parse and return the response
const result = (await response.json()) as { data: string };
return {
content: [{ type: "text", text: JSON.stringify(result.data) }],
};
} catch (error) {
console.error("Error performing code review:", error);
// Handle the error properly with type checking
// This ensures we extract a meaningful message regardless of error type
const errorMessage =
error instanceof Error ? error.message : "Unknown error occurred";
// Return a user-friendly error message in the MCP protocol format
// This allows the AI assistant to understand and communicate the error
return {
content: [
{
type: "text",
text: `Error performing code review: ${errorMessage}`,
},
],
};
}
}
);
/**
* Step 3: Create and export the Lambda handler
*
* We use the Middy middleware framework to:
* 1. Convert HTTP requests to MCP protocol format using mcpMiddleware
* 2. Handle errors using httpErrorHandler
*
* This allows our function to receive HTTP requests from API Gateway
* and process them as MCP protocol interactions.
*/
export const handler = middy()
.use(mcpMiddleware({ server: server as any }))
.use(httpErrorHandler());
#!/usr/bin/env node
import "dotenv/config";
import * as cdk from "aws-cdk-lib";
import { LambdaMcpStack } from "../lib/lambda-mcp-stack";
const app = new cdk.App();
// Generate a random API key if one is not provided
const apiKey =
process.env.API_KEY ||
`api-key-${Math.random().toString(36).substring(2, 15)}`;
// Create the Lambda MCP stack
new LambdaMcpStack(app, "LambdaMcpStack", {
stackEndpoint: process.env.STACK_ENDPOINT || "https://example.com/api",
apiKey: apiKey,
env: {
account: process.env.CDK_ACCOUNT,
region: process.env.CDK_REGION,
},
});
// Display the API key that will be used
console.log(`Using API key: ${apiKey}`);
console.log("Make sure to save this key for configuring your MCP clients.");
https://[YOUR-REGION].console.aws.amazon.com/servicequotas/home/services/apigateway/quotas
# Bootstrap your AWS environment (if you haven't already)
cdk bootstrap
# Deploy the stack
cdk deploy
LambdaMcpStack.ApiUrl = https://abcdef123.execute-api.us-west-2.amazonaws.com/prod/mcp
// Please review this code
function calculateTotal(items: any[]) {
let sum = 0;
for (let i = 0; i < items.length; i++) {
sum += items[i].price * items[i].quantity;
}
return sum;
}
server.tool(
"generate-content",
"Generates SEO-optimized blog content based on a topic and keywords",
{ topic: z.string(), keywords: z.array(z.string()) },
async ({ topic, keywords }) => {
// Implementation that calls your Nocodo content generation workflow
}
);
server.tool(
"analyze-data",
"Analyzes numerical data and provides statistical insights",
{ data: z.array(z.number()), analysisType: z.enum(["basic", "advanced"]) },
async ({ data, analysisType }) => {
// Implementation that calls your Nocodo data analysis workflow
}
);
server.tool(
"summarize-document",
"Creates concise summaries of long documents",
{ document: z.string(), maxLength: z.number().optional() },
async ({ document, maxLength }) => {
// Implementation that calls your Nocodo summarization workflow
}
);
Simplify workflows, automate tasks, and enhance productivity.
All without a single line of code.