DEV Community

Cover image for 🧠🥷How to make Remote MCP 2 (AWS Lambda + Next.js + Cline and Cursor)
Web Developer Hyper
Web Developer Hyper

Posted on

🧠🥷How to make Remote MCP 2 (AWS Lambda + Next.js + Cline and Cursor)

Intro

Hello! I'm Ninja Web Developer. Hi-Yah!🥷

I have been enjoying MCP for a while.
Last time, I tried Remote MCP using Vercel, Next.js, Cline and Cursor. ↓
🧠🥷How to make Remote MCP (Vercel + Next.js + Cline and Cursor)

So, this time, I will try to deploy it on AWS Lambda.
I will use Meme MCP that I made before as a sample code. ↓
🧠🥷How to make Meme Generating MCP (Cline and Cursor)
Let’s start! 🚀

Outline of structure

This system has a simple three layer structure.
Cline or Cursor → MCP on Lambda → Web App on Lambda
1️⃣ Cline or Cursor will send the instruction to MCP.
2️⃣ MCP will relay the instruction to Web App.
3️⃣ Web App will display Meme on the browser.
Note: MCP and Web App is in a same Next.js project.

Technologies Used

To deploy on Vercel, I used @vercel/mcp-adapter. ↓
https://github.com/vercel/mcp-adapter
It was easy to make a MCP server and Next.js Web App in one Next.js project using @vercel/mcp-adapter.
So, I was wondering if it would also work on AWS Lambda, but it worked.

Also, I used AWS Lambda Web Adapter this time. ↓
https://github.com/awslabs/aws-lambda-web-adapter
AWS Lambda Web Adapter lets us use Next.js and other frameworks to run on AWS Lambda.

To keep the data, I used Upstash Redis. ↓
https://upstash.com/
This time, I just wanted to keep a short text data for Meme, so Upstash Redis was enough.
If you need to keep complicated and large things, DynamoDB or S3 would also be a good choice.

To deploy on Lambda, I made a zip file of Next.js and used the AWS Management Console to make things simple.
There are other ways using SAM (Serverless Application Model) and Docker, so I might try them another day.

How to make Remote MCP on AWS Lambda

1️⃣ Make a Next.js project

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

https://nextjs.org/docs/app/getting-started/installation

2️⃣ Install @vercel/mcp-adapter and zod and Upstash Redis

npm install @vercel/mcp-adapte zod @upstash/redis
Enter fullscreen mode Exit fullscreen mode

3️⃣ Set the codes
Code of MCP (app/[transport]/route.ts) ↓

import { createMcpHandler } from "@vercel/mcp-adapter";
import { z } from "zod";

const handler = createMcpHandler(async (server) => {
  server.tool(
    "generate_meme",
    "Generates a meme by sending noText and yesText.",
    {
      noText: z.string(),
      yesText: z.string(),
    },
    async ({ noText, yesText }) => {
      await fetch("https://path to lambda/api/generate-meme", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({ noText, yesText }),
      });
      return {
        content: [{ type: "text", text: `Success!` }],
      };
    }
  );
});

export { handler as GET, handler as POST };
Enter fullscreen mode Exit fullscreen mode

Note: Need to update the URL in the code later. ↑
Note2: Code is same as the previous Vercel version. ↑

Code of frontend (app/page.tsx) ↓

"use client";

import { useState, useEffect } from "react";

export default function Home() {
  const [noText, setNoText] = useState("");
  const [yesText, setYesText] = useState("");
  const [error, setError] = useState<string | null>(null);

  const fetchMemeText = async () => {
    setError(null);
    try {
      const response = await fetch("/api/generate-meme", {
        method: "GET",
      });

      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(
          `HTTP error! status: ${response.status}, body: ${errorText}`
        );
      }

      const data = await response.json();
      if (data.receivedText) {
        setNoText(data.receivedText.noText);
        setYesText(data.receivedText.yesText);
      }
    } catch (error) {
      console.error("Error fetching meme text:", error);
      const errorMessage =
        error instanceof Error ? error.message : "An unknown error occurred";
      setError(errorMessage);
    }
  };

  useEffect(() => {
    const intervalId = setInterval(() => {
      fetchMemeText();
    }, 4000);

    return () => clearInterval(intervalId);
  }, []);

  return (
    <div className="flex flex-col items-center justify-center min-h-screen py-2">
      <h1 className="text-6xl font-bold mb-4">Meme Generating MCP</h1>

      {error && <p className="text-red-500">Error: {error}</p>}

      {noText && yesText && (
        <div>
          <div className="flex items-center">
            <img src="./template_no.png" alt="template_no" className="mr-4" />
            <p className="text-6xl font-bold">{noText}</p>
          </div>
          <div className="flex items-center">
            <img src="./template_yes.png" alt="template_yes" className="mr-4" />
            <p className="text-6xl font-bold">{yesText}</p>
          </div>
        </div>
      )}
    </div>
  );
}

Enter fullscreen mode Exit fullscreen mode

Note: Code is same as the previous Vercel version. ↑

Code of backend (app/api/generate-meme/route.ts) ↓

import { NextResponse } from "next/server";
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL || "",
  token: process.env.UPSTASH_REDIS_REST_TOKEN || "",
});

export async function POST(request: Request) {
  try {
    const { noText, yesText } = await request.json();
    console.log("Received text in Next.js API:", { noText, yesText });

    await redis.set("noText", noText);
    await redis.set("yesText", yesText);

    return NextResponse.json({
      message: "Text received successfully",
      receivedText: { noText, yesText },
    });
  } catch (error) {
    console.error("Error processing request in Next.js API:", error);
    const errorMessage =
      error instanceof Error ? error.message : "An unknown error occurred";
    return NextResponse.json(
      {
        message: "Error processing request",
        error: errorMessage,
      },
      { status: 500 }
    );
  }
}

export async function GET() {
  const noText = await redis.get("noText");
  const yesText = await redis.get("yesText");
  return NextResponse.json({
    message: "Text retrieved successfully",
    receivedText: { noText: noText, yesText: yesText },
  });
}
Enter fullscreen mode Exit fullscreen mode

Note: Using Upstash Redis is the different part as with the Vercel version. ↑

Set "template_no.png" and "template_yes.png" inside "public" folder.
template_no.png ↓
Image description
template_yes.png ↓
Image description

4️⃣ Set Upstash Redis
https://upstash.com/

1 Login, and Create a Redis Database,

2 Check the values of UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN that will be used later.

5️⃣ Deploy on AWS Lambda
I referred to this article. (Japanese) ↓
https://blog.bigdragon.tech/articles/nextjs-deploy-to-lambda-fast
Thank you very much.

1 Create Lambda
Lambda → "Create function" button → input "Function name" → "Additional configurations" → check "Enable function URL" → Auth type "NONE"

2 Prepare Code
Copy the "Function URL" at the right of display, and paste it to URL of "app/[transport]/route.ts"

3 Set next.config.ts
Add "output: 'standalone'" to "next.config.ts". ↓

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  output: 'standalone',
};

export default nextConfig;
Enter fullscreen mode Exit fullscreen mode

4 Build Next.js. ↓

npm run build
Enter fullscreen mode Exit fullscreen mode

5 Copy codes to deploy. ↓

cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/
Enter fullscreen mode Exit fullscreen mode

6 Make a zip file of "standalone" folder.

7 Deploy Code
Lambda → "Code" tab → "Upload from" button → Select ".zip file" → "Upload" button → select the zip file

Make "run.sh" at the root, and paste the code below. ↓

#!/bin/bash

node ./standalone/server.js
Enter fullscreen mode Exit fullscreen mode

Push "Deploy" button.

8 Set Runtime
"Code" tab → "Runtime settings" → "Edit" button → Change handler to "run.sh" → "Save" button

9 Set Lambda Web Adapter
"Code" tab → "Layers" → "Add a layer" button → select "Specify an ARN" → "arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerX86:25" (replace the Region part) → "Verify" button → "Add" button

10 Set Environment Variables
"Configuration" tab → "Environment Variables" → "Edit" button → "Add environment variable" button ↓

Key Value
AWS_LAMBDA_EXEC_WRAPPER /opt/bootstrap
PORT 3000
UPSTASH_REDIS_REST_URL https://xxxxxx.upstash.io
UPSTASH_REDIS_REST_TOKEN xxxxxxxxxxxx

Note: Replace URL and token with real values from Upstash Redis. ↑

6️⃣ Set Cline and Cursor
Set cline_mcp_settings.json for Cline and mcp.json for Cursor.

Cline (Streamable HTTP)

{
  "mcpServers": {
    "meme-mcp-server": {
      "command": "npx",
      "args": ["mcp-remote", "https://path to lambda/mcp"],
      "transportType": "Streamable HTTP"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cline (SSE)

{
  "mcpServers": {
    "meme-mcp-server": {
      "command": "npx",
      "args": ["mcp-remote", "https://path to lambda/sse"],
      "transportType": "SSE"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cursor (Streamable HTTP)

{
  "mcpServers": {
    "meme-mcp-server": {
      "url": "https://path to lambda/mcp"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cursor (SSE)

{
  "mcpServers": {
    "meme-mcp-server": {
      "url": "https://path to lambda/sse"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

How to use

1️⃣ Open this App on Lambda

2️⃣ Ask your Cline or Cursor to generate a Meme.
For example,

Use "generate_meme" tool of "meme-mcp-server",
 and send "noText": “Before watching Meme Monday”
 and “yesText”: “After watching Meme Monday”.
Enter fullscreen mode Exit fullscreen mode

Image description

3️⃣ Or ask your Cline or Cursor to think one of the text instead of you.
For example,

Use "generate_meme" tool of "meme-mcp-server",
 and send "noText": “”
 and “yesText”: “After watching Meme Monday”. 
Think the noText part and fill in the text.
Enter fullscreen mode Exit fullscreen mode

Image description

4️⃣ Or ask your Cline or Cursor to think both texts instead of you.
For example,

Use "generate_meme" tool of "meme-mcp-server",
 and send "noText": “Before watching Meme Monday”
 and “yesText”: “After watching Meme Monday”. 
Think unique and humorous texts and replace noText and yesText.
Enter fullscreen mode Exit fullscreen mode

Image description

5️⃣ Yay! We can generate Memes from Cline and Cursor.🎉

Caution❗

After testing this App, do not forget to delete the App from Lambda.
Select your Lambda → "Actions" button → Select "Delete" → Input "confirm" → "Delete" button
In this App, frontend calls the backend for 4 seconds interval, so it will keep sending requests.

Outro

Today, I wrote about how to make Remote MCP on AWS Lambda using Next.js, @vercel/mcp-adapter, AWS Lambda Web Adapter, Upstash Redis, Cline and Cursor.

AWS is also releasing lots of official AWS MCP Servers, so I want to check them out someday. ↓
https://awslabs.github.io/mcp/

I hope you will learn something from this post.😊
Thank you for reading.
Happy AI coding!🤖 Hi-Yah!🥷

Top comments (3)

Collapse
 
dotallio profile image
Dotallio

Loved how clearly you broke down deploying Next.js MCP with AWS Lambda and Upstash Redis! Did you notice any cold start issues with Lambda for this setup?

Collapse
 
webdeveloperhyper profile image
Web Developer Hyper

Thank you for checking my post. 😃
In my environment, Lambda seems to be running fine.
However, the world of AWS is so deep, so I need much more learning. 📝

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more