DEV Community

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

Posted on • Edited on

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

Update (2025/06/08): Wrote about How to make Remote MCP 2 (AWS Lambda + Next.js + Cline and Cursor)
🧠🥷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. ↓
🧠🥷MCP Transports (Image generation and editing MCP Ver 2.0 (WebSocket + Next.js + Gemini API + Cline and Cursor))
I had an image that MCP sever runs at local, but there seems to be ways to run MCP remotely.
So, this time I tried to make a MCP server with Next.js and deploy it on Vercel.
I would remake Meme MCP that I made before for a sample. ↓
🧠🥷How to make Meme Generating MCP (Cline and Cursor)

Outline of structure

This system has a simple three layer structure.
Cline or Cursor → MCP on Vercel → Web App on Vercel
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.

To deploy on Vercel, I referred to this article of Vercel. ↓
https://vercel.com/changelog/mcp-server-support-on-vercel
I used library @vercel/mcp-adapter, and rewrite the Meme MCP server to adjust to @vercel/mcp-adapter. ↓
https://www.npmjs.com/package/@vercel/mcp-adapter
The code got simpler than previous one.

How to make Remote MCP on Vercel

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

npm install @vercel/mcp-adapte zod
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 vercel/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.

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 local running version.

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

import { NextResponse } from "next/server";

let storedNoText: string = "";
let storedYesText: string = "";

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

    storedNoText = noText;
    storedYesText = 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() {
  return NextResponse.json({
    message: "Text retrieved successfully",
    receivedText: { noText: storedNoText, yesText: storedYesText },
  });
}
Enter fullscreen mode Exit fullscreen mode

Note: Code is same as the previous local running version.

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

4️⃣ Push the project to GitHub

5️⃣ Deploy on Vercel

1 Deplay on Vercel
"Overview" tab → "Add New" button → select "Project" → select the repository you want to use → "Import" button → "Deploy" button

2 Set Redis
"Storage" tab → "Create Database" button → select "Upstash (or Redis)" → select "Upstash for Redis" → select "Primary Regin" and "Plans" → "Continue" button → "Create" Button → "Connet" button

3 Update URL
"Overview" tab → copy the URL of "Domains" → update the URL in (app/[transport]/route.ts) of GitHub → Vercel will deplay the code again automatically.

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 vercel/mcp"],
      "transportType": "Streamable HTTP"
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Cline (SSE)

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

Cursor (Streamable HTTP)

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

Cursor (SSE)

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

How to use

1️⃣ Open your App on Vercel

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 Vercel.
"Settings" → "Delete Project"
In this App, frontend calls the backend for 4 seconds interval, so it will keep sending requests.

Outro

Remote MCP will broaden the possibility of MCP.
Next time I would like to challenge deploying MCP server to AWS lambda if I could.

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

Top comments (0)