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
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
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 };
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>
);
}
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 },
});
}
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 ↓
template_yes.png ↓
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;
4 Build Next.js. ↓
npm run build
5 Copy codes to deploy. ↓
cp -r public .next/standalone/ && cp -r .next/static .next/standalone/.next/
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
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"
}
}
}
Cline (SSE)
{
"mcpServers": {
"meme-mcp-server": {
"command": "npx",
"args": ["mcp-remote", "https://path to lambda/sse"],
"transportType": "SSE"
}
}
}
Cursor (Streamable HTTP)
{
"mcpServers": {
"meme-mcp-server": {
"url": "https://path to lambda/mcp"
}
}
}
Cursor (SSE)
{
"mcpServers": {
"meme-mcp-server": {
"url": "https://path to lambda/sse"
}
}
}
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”.
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.
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.
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)
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?
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