How to Deploy a Remote MCP Server on Vercel: Complete Step-by-Step Guide 2025

Learn how to build and deploy your own remote MCP server on Vercel's serverless platform. Complete guide with real-world example using contextstudios-mcp, covering Streamable HTTP transport, tool registration, and client integration.

How to Deploy a Remote MCP Server on Vercel: Complete Step-by-Step Guide 2025

How to Deploy a Remote MCP Server on Vercel: Complete Step-by-Step Guide 2025

The Model Context Protocol (MCP) is revolutionizing how AI applications interact with external tools and data sources. In this comprehensive guide, we'll walk through deploying your own remote MCP server on Vercel's serverless platform, using our production my-mcp-server server as a real-world example.

What is the Model Context Protocol (MCP)?

MCP is an open standard developed by Anthropic that enables large language models (LLMs) to communicate seamlessly with external systems. Think of it as a universal adapter that allows AI assistants like Claude, ChatGPT, and others to access your custom tools, databases, and APIs in a standardized way.

Key Benefits of MCP

  • Standardized Communication: One protocol to rule them all - no more custom integrations for each AI platform
  • Tool Discovery: AI clients can automatically discover what tools your server provides
  • Type Safety: Built-in schemas ensure data validation and proper error handling
  • Extensibility: Easy to add new tools without changing the core architecture

Why Deploy MCP on Vercel?

Vercel officially announced MCP server support in May 2025, making it the premier platform for hosting remote MCP servers. Here's why:

  1. Serverless Architecture: Pay only for what you use, with automatic scaling
  2. Global Edge Network: Low latency access from anywhere in the world
  3. Built-in Support: Native Streamable HTTP transport support
  4. Easy Deployment: Push to Git and deploy automatically
  5. Fluid Compute: Efficient resource utilization for AI workloads

Architecture Overview

Before diving into the code, let's understand the architecture:

┌──────────────────┐     HTTPS      ┌──────────────────────┐
│                  │ ◄────────────► │                      │
│   MCP Client     │   Streamable   │  Vercel Function     │
│  (Claude Code,   │     HTTP       │  (MCP Server)        │
│   Cursor, etc.)  │                │                      │
│                  │                │  ┌────────────────┐  │
└──────────────────┘                │  │  Tool Handler  │  │
                                    │  ├────────────────┤  │
                                    │  │  Tool Handler  │  │
                                    │  ├────────────────┤  │
                                    │  │  Tool Handler  │  │
                                    │  └────────────────┘  │
                                    │                      │
                                    └──────────────────────┘

Why Streamable HTTP?

The Streamable HTTP transport (introduced in the MCP specification 2025-03-26) is the recommended protocol for remote MCP servers. It offers:

  • Efficient connection reuse
  • Better concurrency handling than SSE
  • Stateless or stateful operation modes
  • Lower TCP connection overhead

Step 1: Project Setup

First, create a new project or use an existing Next.js/Node.js project:

# Create new project
mkdir my-mcp-server
cd my-mcp-server
npm init -y

# Install dependencies
npm install @modelcontextprotocol/sdk zod
npm install -D @vercel/node typescript @types/node

Create your tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "bundler",
    "esModuleInterop": true,
    "strict": true,
    "outDir": "dist",
    "declaration": true
  },
  "include": ["src/**/*", "api/**/*"]
}

Step 2: Create the MCP Server Handler

Create the main API handler at api/index.ts. This is where the magic happens:

Setting Up the Server Factory

import type { VercelRequest, VercelResponse } from "@vercel/node";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { z } from "zod";

// Create MCP Server Factory
function createMcpServer(): McpServer {
  const server = new McpServer({
    name: "my-mcp-server",
    version: "1.0.0",
  });

  // Register your first tool
  server.registerTool("hello_world", {
    title: "Hello World",
    description: "A simple greeting tool",
    inputSchema: {
      name: z.string().describe("Name to greet"),
    },
  }, async ({ name }) => {
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          message: `Hello, ${name}! Welcome to MCP.`,
          timestamp: new Date().toISOString(),
        }, null, 2),
      }],
    };
  });

  return server;
}

Implementing the Request Handler

// Main API Handler
export default async function handler(
  req: VercelRequest,
  res: VercelResponse
) {
  // CORS headers for cross-origin requests
  res.setHeader("Access-Control-Allow-Origin", "*");
  res.setHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, OPTIONS");
  res.setHeader("Access-Control-Allow-Headers",
    "Content-Type, Authorization, mcp-session-id, mcp-protocol-version");
  res.setHeader("Access-Control-Expose-Headers",
    "mcp-session-id, mcp-protocol-version");

  // Handle preflight requests
  if (req.method === "OPTIONS") {
    return res.status(200).end();
  }

  // Health check endpoint
  if (req.method === "GET" && req.query.action === "health") {
    return res.status(200).json({
      status: "ok",
      server: "my-mcp-server",
      version: "1.0.0",
    });
  }

  // Handle MCP protocol requests
  try {
    const server = createMcpServer();
    const transport = new StreamableHTTPServerTransport({
      sessionIdGenerator: undefined, // Stateless mode for serverless
    });

    await server.connect(transport);
    await transport.handleRequest(req, res, req.body);

    // Cleanup after response
    res.on("finish", async () => {
      try {
        await transport.close();
        await server.close();
      } catch {
        // Ignore cleanup errors
      }
    });
  } catch (error) {
    if (!res.headersSent) {
      return res.status(500).json({
        jsonrpc: "2.0",
        error: {
          code: -32603,
          message: `Internal server error: ${error}`,
        },
        id: req.body?.id || null,
      });
    }
  }
}

Step 3: Configure Vercel Deployment

Create vercel.json for proper configuration:

{
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "version": 2,
  "buildCommand": "npm run build",
  "functions": {
    "api/index.ts": {
      "maxDuration": 60
    }
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" },
        { "key": "Access-Control-Allow-Methods", "value": "GET, POST, OPTIONS" },
        { "key": "Access-Control-Allow-Headers", "value": "Content-Type, Authorization" }
      ]
    }
  ]
}

Step 4: Adding Real Tools - The my-mcp-server Example

Let's look at how our production my-mcp-server server implements real tools. This server provides web search, social media publishing, image generation, and more.

Web Search Tool

// Web Search Tool
server.registerTool("search_web", {
  title: "Search Web",
  description: "Search the web for current information using Tavily AI",
  inputSchema: {
    query: z.string().describe("Search query"),
    maxResults: z.number().optional().describe("Maximum results (default: 5)"),
    topic: z.enum(["general", "news"]).optional().describe("Search type"),
  },
}, async ({ query, maxResults, topic }) => {
  const result = await tavilySearch(query, { maxResults, topic });
  return {
    content: [{
      type: "text",
      text: JSON.stringify({
        query: result.query,
        answer: result.answer,
        results: result.results,
      }, null, 2),
    }],
  };
});

Image Generation Tool

// Image Generation Tool
server.registerTool("generate_hero_image", {
  title: "Generate Hero Image",
  description: "Generate a hero image using Gemini AI",
  inputSchema: {
    topic: z.string().describe("Image topic/description"),
    style: z.enum([
      "modern-professional",
      "tech-gradient",
      "minimal-abstract"
    ]).describe("Visual style"),
  },
}, async ({ topic, style }) => {
  const image = await generateImageWithGemini(topic, style);
  return {
    content: [{
      type: "text",
      text: JSON.stringify({
        status: "success",
        topic,
        style,
        mimeType: image.mimeType,
        base64Data: image.base64Data,
      }, null, 2),
    }],
  };
});

Step 5: Environment Variables

Set up your environment variables in Vercel:

# Required for various integrations
TAVILY_API_KEY=your_tavily_key
GOOGLE_API_KEY=your_google_key
TYPEFULLY_API_KEY=your_typefully_key

Step 6: Deploy to Vercel

Deploy your MCP server:

# Install Vercel CLI
npm install -g vercel

# Deploy
vercel

# For production
vercel --prod

After deployment, your server will be available at: https://your-project.vercel.app/api

Step 7: Connect Clients to Your MCP Server

Connecting Claude Code / Claude Desktop

Add to your Claude configuration (typically at ~/.claude.json or settings.json):

{
  "mcpServers": {
    "my-mcp-server": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote",
        "https://your-project.vercel.app/api"
      ]
    }
  }
}

Connecting with the AI SDK (Vercel)

import { experimental_createMCPClient } from "ai";

const mcpClient = await experimental_createMCPClient({
  transport: {
    type: "sse",
    url: "https://your-project.vercel.app/api",
  },
});

// Get available tools
const tools = await mcpClient.tools();
console.log("Available tools:", tools);

Step 8: Testing Your Server

Test your server is working:

# Health check
curl https://your-project.vercel.app/api?action=health

# List tools (via mcp-remote)
npx mcp-remote https://your-project.vercel.app/api --list-tools

Best Practices

1. Stateless Design

Vercel Functions are stateless. Design your tools accordingly:

// Good: Stateless operation
const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: undefined, // Stateless
});

2. Error Handling

Always return proper JSON-RPC error responses:

} catch (error) {
  return res.status(500).json({
    jsonrpc: "2.0",
    error: {
      code: -32603,
      message: `Internal error: ${error.message}`,
    },
    id: req.body?.id || null,
  });
}

3. Timeout Management

Set appropriate timeouts in vercel.json:

{
  "functions": {
    "api/index.ts": {
      "maxDuration": 60
    }
  }
}

4. Input Validation with Zod

Always validate inputs with Zod schemas:

inputSchema: {
  query: z.string().min(1).max(1000),
  limit: z.number().min(1).max(100).optional(),
},

Next Steps

Now that you understand how to deploy an MCP server on Vercel, here are some ideas for tools you could build:

  • Research Tools: Web search, news aggregation, topic research
  • Content Tools: Blog outline generation, SEO optimization
  • Image Tools: AI image generation with various styles
  • Database Tools: Query and manage your databases
  • API Integrations: Connect to third-party services

Conclusion

Deploying an MCP server on Vercel combines the power of serverless computing with the flexibility of the Model Context Protocol. By following this guide, you can create production-ready AI tool integrations that work across Claude, ChatGPT, and other MCP-compatible clients.

The MCP ecosystem is growing rapidly, with major companies like Booking.com, Expedia, Morningstar, and Microsoft adopting the protocol. Now is the perfect time to build your own MCP integrations and join this expanding ecosystem.

Resources


Ready to deploy your own MCP server? Start with our template and have your AI-powered tools live in minutes!

Share article

Share: