25 min read
Advanced
Deployment

Serverless MCP on AWS Lambda

Deploy MCP servers as serverless functions on AWS Lambda with API Gateway, cold start optimization, and cost management

MCPgee Team

MCP Expert

AWS account with Lambda and API Gateway accessAWS CLI configured locallyA working MCP server (TypeScript or Python)Basic understanding of serverless architecture

Serverless MCP on AWS Lambda

Introduction

AWS Lambda lets you run MCP servers without managing infrastructure. You pay only for the compute time you use, making it ideal for MCP servers with variable or unpredictable traffic patterns. This tutorial covers deploying MCP servers to Lambda, connecting them via API Gateway, handling cold starts, and optimizing for cost and performance.

For container-based deployments, see our Docker and Kubernetes tutorials instead.

Architecture Overview

plaintext
┌──────────────┐     ┌─────────────────┐     ┌──────────────┐
│  MCP Client  │ --> │  API Gateway    │ --> │  Lambda      │
│  (Claude,    │ <-- │  (HTTP Endpoint)│ <-- │  (MCP Server)│
│   VS Code)   │     └─────────────────┘     └──────────────┘

The MCP client connects to your Lambda function through API Gateway using Streamable HTTP transport. API Gateway handles TLS termination, authentication, and request routing.

TypeScript Lambda MCP Server

Project Setup

bash
mkdir mcp-lambda-server
cd mcp-lambda-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node @types/aws-lambda esbuild

Lambda Handler

typescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';

// Create server instance outside handler for reuse across invocations
const server = new McpServer({
  name: 'lambda-mcp-server',
  version: '1.0.0',
});

// Register tools
server.tool(
  'get-weather',
  'Get current weather for a city',
  { city: z.string() },
  async ({ city }) => ({
    content: [{
      type: 'text',
      text: JSON.stringify({ city, temperature: 72, condition: 'sunny' }),
    }],
  })
);

server.tool(
  'convert-currency',
  'Convert between currencies',
  {
    amount: z.number(),
    from: z.string().length(3),
    to: z.string().length(3),
  },
  async ({ amount, from, to }) => ({
    content: [{
      type: 'text',
      text: JSON.stringify({ amount, from, to, result: amount * 0.85, rate: 0.85 }),
    }],
  })
);

export async function handler(
  event: APIGatewayProxyEventV2
): Promise<APIGatewayProxyResultV2> {
  const method = event.requestContext.http.method;
  const body = event.body ? JSON.parse(event.body) : null;

  if (method === 'POST' && body) {
    try {
      // Process JSON-RPC request through MCP server
      const response = await processRequest(server, body);
      return {
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(response),
      };
    } catch (error) {
      return {
        statusCode: 500,
        body: JSON.stringify({
          jsonrpc: '2.0',
          error: { code: -32603, message: 'Internal error' },
          id: body?.id || null,
        }),
      };
    }
  }

  return {
    statusCode: 405,
    body: JSON.stringify({ error: 'Method not allowed' }),
  };
}

Bundle with esbuild

bash
npx esbuild src/handler.ts --bundle --platform=node --target=node20 --outfile=dist/handler.js --format=esm --external:@aws-sdk/*

Python Lambda MCP Server

Lambda Handler

python
import json
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("lambda-server")

@mcp.tool()
def analyze_text(text: str) -> str:
    """Analyze text and return statistics.

    Args:
        text: Text to analyze
    """
    words = text.split()
    return json.dumps({
        "word_count": len(words),
        "char_count": len(text),
        "unique_words": len(set(w.lower() for w in words)),
        "avg_word_length": round(sum(len(w) for w in words) / len(words), 1) if words else 0,
    })

@mcp.tool()
def generate_slug(title: str) -> str:
    """Generate a URL slug from a title.

    Args:
        title: Title to slugify
    """
    import re
    slug = title.lower().strip()
    slug = re.sub(r'[^a-z0-9\s-]', '', slug)
    slug = re.sub(r'[\s-]+', '-', slug)
    return slug

def handler(event, context):
    method = event.get("requestContext", {}).get("http", {}).get("method", "")
    body = json.loads(event.get("body", "{}"))

    if method == "POST" and body:
        try:
            response = process_mcp_request(mcp, body)
            return {
                "statusCode": 200,
                "headers": {"Content-Type": "application/json"},
                "body": json.dumps(response),
            }
        except Exception as e:
            return {
                "statusCode": 500,
                "body": json.dumps({
                    "jsonrpc": "2.0",
                    "error": {"code": -32603, "message": str(e)},
                    "id": body.get("id"),
                }),
            }

    return {"statusCode": 405, "body": json.dumps({"error": "Method not allowed"})}

AWS SAM Deployment

SAM Template

yaml
# template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: MCP Server on AWS Lambda

Globals:
  Function:
    Timeout: 30
    Runtime: nodejs20.x
    MemorySize: 256

Resources:
  MCPServerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handler.handler
      CodeUri: dist/
      Description: MCP Server Lambda Function
      Environment:
        Variables:
          NODE_ENV: production
      Events:
        MCPApi:
          Type: HttpApi
          Properties:
            Path: /mcp
            Method: POST
        MCPApiGet:
          Type: HttpApi
          Properties:
            Path: /mcp
            Method: GET
        MCPApiDelete:
          Type: HttpApi
          Properties:
            Path: /mcp
            Method: DELETE

  MCPServerFunctionUrl:
    Type: AWS::Lambda::Url
    Properties:
      TargetFunctionArn: !Ref MCPServerFunction
      AuthType: AWS_IAM
      InvokeMode: RESPONSE_STREAM

Outputs:
  MCPEndpoint:
    Description: MCP Server API Endpoint
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/mcp"

Deploy

bash
sam build
sam deploy --guided

CDK Deployment (Alternative)

typescript
import * as cdk from 'aws-cdk-lib';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as apigateway from 'aws-cdk-lib/aws-apigatewayv2';

export class McpServerStack extends cdk.Stack {
  constructor(scope: cdk.App, id: string) {
    super(scope, id);

    const fn = new lambda.Function(this, 'MCPServer', {
      runtime: lambda.Runtime.NODEJS_20_X,
      handler: 'handler.handler',
      code: lambda.Code.fromAsset('dist'),
      memorySize: 256,
      timeout: cdk.Duration.seconds(30),
      environment: {
        NODE_ENV: 'production',
      },
    });

    const httpApi = new apigateway.HttpApi(this, 'MCPApi', {
      defaultIntegration: new apigateway.HttpLambdaIntegration('MCPIntegration', fn),
    });

    new cdk.CfnOutput(this, 'Endpoint', {
      value: httpApi.url + 'mcp',
    });
  }
}

Cold Start Optimization

Lambda cold starts can add 1-5 seconds of latency. Strategies to minimize impact:

1. Provisioned Concurrency

Keep a minimum number of Lambda instances warm:

yaml
Resources:
  MCPServerFunction:
    Properties:
      ProvisionedConcurrencyConfig:
        ProvisionedConcurrentExecutions: 2

2. Module Initialization Outside Handler

Initialize expensive objects outside the handler function:

typescript
// These run once during cold start, then reuse across invocations
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';

const server = new McpServer({ name: 'lambda-server', version: '1.0.0' });
// Register all tools here

// Handler reuses the pre-initialized server
export async function handler(event) {
  // ... use server
}

3. Keep Bundles Small

Use tree-shaking and minimize dependencies:

bash
npx esbuild src/handler.ts --bundle --minify --tree-shaking=true --platform=node --outfile=dist/handler.js

4. Use ARM Architecture

Graviton2 (ARM) functions start faster and cost less:

yaml
Resources:
  MCPServerFunction:
    Properties:
      Architectures:
        - arm64

Authentication

API Gateway Authorizers

yaml
Resources:
  MCPApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      Auth:
        DefaultAuthorizer: JWTAuthorizer
        Authorizers:
          JWTAuthorizer:
            AuthorizationScopes:
              - mcp:read
              - mcp:write
            IdentitySource: $request.header.Authorization
            JwtConfiguration:
              issuer: https://your-auth-provider.com
              audience:
                - your-audience-id

Lambda Authorizer

For custom authentication logic:

python
def authorizer(event, context):
    token = event.get("headers", {}).get("authorization", "")
    if validate_token(token):
        return {"isAuthorized": True}
    return {"isAuthorized": False}

For comprehensive authentication patterns, see our MCP authentication tutorial.

Cost Optimization

Right-Size Memory

Lambda CPU scales with memory. Test different memory sizes:

bash
# Use AWS Lambda Power Tuning to find optimal memory
aws lambda invoke --function-name mcp-server --payload '{"test": true}' output.json

Reserved Concurrency

Set maximum concurrent executions to control costs:

yaml
Resources:
  MCPServerFunction:
    Properties:
      ReservedConcurrentExecutions: 100

Cost Estimation

Typical MCP server costs on Lambda:

  • Low traffic (1K requests/day): ~\$0.50/month
  • Medium traffic (100K requests/day): ~\$15/month
  • High traffic (1M requests/day): ~\$100/month

Costs vary based on memory, execution time, and region.

Monitoring

CloudWatch Logs

Lambda automatically sends logs to CloudWatch. Add structured logging:

typescript
console.log(JSON.stringify({
  level: 'info',
  tool: toolName,
  duration: endTime - startTime,
  requestId: context.awsRequestId,
}));

CloudWatch Alarms

yaml
Resources:
  ErrorAlarm:
    Type: AWS::CloudWatch::Alarm
    Properties:
      MetricName: Errors
      Namespace: AWS/Lambda
      Statistic: Sum
      Period: 300
      EvaluationPeriods: 1
      Threshold: 5
      ComparisonOperator: GreaterThanThreshold
      Dimensions:
        - Name: FunctionName
          Value: !Ref MCPServerFunction

Limitations and Considerations

Lambda has constraints to keep in mind:

  • 15-minute max execution time: Not suitable for very long-running MCP tools
  • Payload size limits: 6MB synchronous, 256KB asynchronous
  • Cold starts: Can add 1-5s latency on first request
  • Stateless: No persistent in-memory state between invocations

For workloads that exceed these limits, consider Docker or Kubernetes deployments.

Conclusion

AWS Lambda provides a cost-effective and scalable deployment model for MCP servers. With proper cold start optimization, authentication, and monitoring, you can run production MCP servers without managing any infrastructure. The pay-per-use model makes Lambda especially attractive for MCP servers with variable traffic patterns.

For more AWS MCP server examples, explore the AWS Labs MCP servers in our directory.

Code Examples

TypeScript Lambda Handlertypescript
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import type { APIGatewayProxyEventV2, APIGatewayProxyResultV2 } from 'aws-lambda';

const server = new McpServer({ name: 'lambda-server', version: '1.0.0' });

server.tool('hello', 'Greet someone', { name: z.string() }, async ({ name }) => ({
  content: [{ type: 'text', text: `Hello, ${name}!` }],
}));

export async function handler(event: APIGatewayProxyEventV2): Promise<APIGatewayProxyResultV2> {
  const body = event.body ? JSON.parse(event.body) : null;
  if (!body) return { statusCode: 400, body: 'Missing body' };

  const response = await processRequest(server, body);
  return {
    statusCode: 200,
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(response),
  };
}
SAM Templateyaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

Resources:
  MCPServerFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handler.handler
      Runtime: nodejs20.x
      CodeUri: dist/
      MemorySize: 256
      Timeout: 30
      Architectures: [arm64]
      Events:
        MCPApi:
          Type: HttpApi
          Properties:
            Path: /mcp
            Method: POST

Outputs:
  Endpoint:
    Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/mcp"
Python Lambda Handlerpython
import json
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("lambda-server")

@mcp.tool()
def analyze(text: str) -> str:
    """Analyze text content.

    Args:
        text: Text to analyze
    """
    words = text.split()
    return json.dumps({
        "words": len(words),
        "chars": len(text),
        "unique": len(set(w.lower() for w in words)),
    })

def handler(event, context):
    body = json.loads(event.get("body", "{}"))
    response = process_mcp_request(mcp, body)
    return {
        "statusCode": 200,
        "headers": {"Content-Type": "application/json"},
        "body": json.dumps(response),
    }

Key Takeaways

  • AWS Lambda provides cost-effective serverless hosting for MCP servers with pay-per-use pricing
  • Initialize MCP server instances outside the handler to reuse across invocations
  • Use provisioned concurrency and ARM architecture to minimize cold start latency
  • API Gateway handles TLS, authentication, and routing for your Lambda MCP server
  • Lambda has a 15-minute execution limit, so it is best suited for quick tool operations

Troubleshooting

Lambda function times out during MCP tool execution

Increase the Lambda timeout (max 900 seconds) in your SAM template. If the tool genuinely needs more than 15 minutes, consider moving to a container-based deployment with ECS or Kubernetes.

Cold start adds several seconds of latency

Enable provisioned concurrency for consistent performance. Use ARM architecture (arm64) for faster cold starts. Minimize bundle size with tree-shaking and keep dependencies minimal.

API Gateway returns 500 errors for MCP requests

Check CloudWatch Logs for the Lambda function to see the actual error. Common issues include missing environment variables, incorrect handler path, or unhandled exceptions in tool implementations.

Next Steps

  • Set up CI/CD with SAM or CDK for automated deployments
  • Add monitoring with CloudWatch dashboards and alarms
  • Implement authentication with API Gateway authorizers
  • Explore container-based alternatives for long-running operations

Was this helpful?

Share tutorial:

Stay Updated with MCP Insights

Join 5,000+ developers and get weekly insights on MCP development, new server releases, and implementation strategies delivered to your inbox.

We respect your privacy. Unsubscribe at any time.

MCPgee Team

We write in-depth guides, tutorials, and reviews to help developers get the most out of the Model Context Protocol ecosystem.

Frequently Asked Questions

Explore MCP Servers

Browse our directory of 33,000+ MCP servers. Find the perfect tools for your AI-powered workflows.