Skip to content

Conversation

@nickytonline
Copy link
Owner

Overview

This PR adds Vercel serverless deployment support to the MCP TypeScript template, enabling easy deployment to Vercel's edge network without managing infrastructure.

What's New

🚀 Serverless Architecture

  • Converted Express-based server to Vercel serverless functions
  • Created /api/mcp.ts for MCP protocol handling
  • Created /api/index.ts for server information endpoint

📦 Shared Libraries

  • Moved common code to /lib directory for reuse across serverless functions
  • lib/config.ts - Environment configuration
  • lib/logger.ts - Structured logging with Pino
  • lib/utils.ts - MCP helper utilities

⚙️ Configuration

  • Added vercel.json with optimized serverless settings
  • Updated package.json with Vercel dependencies and scripts
  • Updated tsconfig.json to support multi-directory structure
  • Updated .gitignore for Vercel-specific files

📚 Documentation

  • README.vercel.md - Comprehensive deployment guide
  • VERCEL_GUIDE.md - Quick reference for developers

Key Features

✅ One-click deployment to Vercel
✅ Automatic scaling based on demand
✅ Zero infrastructure management
✅ Compatible with existing Express version
✅ TypeScript with full type safety
✅ Development server with hot reload
✅ Production-optimized builds

Architecture

Vercel Serverless Functions
│
├── /api/index.ts    → GET /      (Server info)
├── /api/mcp.ts      → POST /mcp  (MCP protocol)
│
└── /lib/           → Shared utilities
    ├── config.ts
    ├── logger.ts
    └── utils.ts

Usage

Local Development

npm install
npm run dev

Deploy to Vercel

# Install Vercel CLI
npm i -g vercel

# Deploy
vercel

Or use the one-click deploy button!

Important Notes

⚠️ Session Management Limitation

The current implementation uses in-memory session storage, which is not production-ready for serverless:

  • Sessions are lost when functions scale down
  • Not shared across function instances
  • Works for demos and light testing only

For production, implement persistent storage using:

  • Vercel KV (recommended)
  • Redis
  • Any other persistent datastore

See README.vercel.md for implementation examples.

💡 Benefits Over Express

  • No server management - Vercel handles infrastructure
  • Automatic scaling - Scale to zero, scale to millions
  • Global distribution - Deploy to edge locations worldwide
  • Pay-per-use - Only pay for actual usage

🔄 Backwards Compatible

The original Express server in src/index.ts remains functional. You can still:

npm run dev:express  # Run Express server
npm start            # Production Express server

Testing

Test the deployed serverless functions:

# Get server info
curl https://your-deployment.vercel.app/

# Initialize MCP session
curl -X POST https://your-deployment.vercel.app/mcp \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "method": "initialize",
    "params": {
      "protocolVersion": "2024-11-05",
      "capabilities": {},
      "clientInfo": {
        "name": "test-client",
        "version": "1.0.0"
      }
    },
    "id": 1
  }'

Files Changed

New Files

  • api/index.ts - Server info endpoint
  • api/mcp.ts - MCP protocol handler
  • lib/config.ts - Shared configuration
  • lib/logger.ts - Shared logging
  • lib/utils.ts - Shared utilities
  • vercel.json - Vercel platform config
  • README.vercel.md - Deployment documentation
  • VERCEL_GUIDE.md - Quick reference guide

Modified Files

  • package.json - Added Vercel dependencies and scripts
  • tsconfig.json - Updated for multi-directory structure
  • .gitignore - Added .vercel directory

Next Steps

After merging:

  1. Update main README to mention serverless option
  2. Add deployment status badge
  3. Create example environment variables
  4. Add Vercel KV session storage example
  5. Set up CI/CD with Vercel

Documentation

Questions?

Feel free to ask questions or request changes. This implementation prioritizes:

  • Ease of deployment
  • Developer experience
  • Production best practices (with noted limitations)
  • Backwards compatibility

Ready to deploy! 🚀

Copilot AI review requested due to automatic review settings November 20, 2025 01:54
Copilot finished reviewing on behalf of nickytonline November 20, 2025 02:01
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR adds Vercel serverless deployment support to the MCP TypeScript template, enabling deployment to Vercel's serverless platform alongside the existing Express server implementation.

Key Changes

  • Introduced serverless function handlers in /api directory for MCP protocol handling and server information endpoints
  • Created shared library code in /lib for configuration, logging, and utilities that can be reused across both Express and serverless implementations
  • Added Vercel-specific configuration and comprehensive deployment documentation

Reviewed Changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
api/mcp.ts Main MCP protocol handler for Vercel serverless, includes session management and request routing
api/index.ts Server information endpoint returning metadata about the MCP server
lib/config.ts Shared environment configuration with Zod validation
lib/logger.ts Shared structured logging setup with Pino
lib/utils.ts Shared MCP helper utilities for formatting responses
vercel.json Vercel platform configuration for serverless functions, rewrites, and runtime settings
package.json Updated build scripts for Vercel deployment, added Vercel dependencies, included lib/ in build
tsconfig.json Expanded to include api/ and lib/ directories, changed from noEmit to outDir configuration
.gitignore Added .vercel directory exclusion
README.vercel.md Comprehensive deployment guide with architecture details and production recommendations
VERCEL_GUIDE.md Quick reference guide for Vercel serverless deployment

Comment on lines +82 to +140
const expressReq = {
body: req.body,
headers: req.headers,
method: req.method,
};

const expressRes = {
status: (code: number) => {
res.status(code);
return expressRes;
},
setHeader: (name: string, value: string) => {
res.setHeader(name, value);
return expressRes;
},
json: (data: any) => {
res.json(data);
},
send: (data: any) => {
res.send(data);
},
end: () => {
res.end();
},
};

await transport.handleRequest(expressReq as any, expressRes as any, req.body);
return;
}

// Handle existing session requests
if (sessionId && transports[sessionId]) {
const transport = transports[sessionId];

const expressReq = {
body: req.body,
headers: req.headers,
method: req.method,
};

const expressRes = {
status: (code: number) => {
res.status(code);
return expressRes;
},
setHeader: (name: string, value: string) => {
res.setHeader(name, value);
return expressRes;
},
json: (data: any) => {
res.json(data);
},
send: (data: any) => {
res.send(data);
},
end: () => {
res.end();
},
};
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code duplication: The Express request/response adapter logic is duplicated between lines 82-106 and 116-140. Consider extracting this into a reusable function to reduce duplication and improve maintainability.

Example:

function createExpressAdapter(res: VercelResponse) {
  return {
    status: (code: number) => {
      res.status(code);
      return expressRes;
    },
    setHeader: (name: string, value: string) => {
      res.setHeader(name, value);
      return expressRes;
    },
    json: (data: any) => {
      res.json(data);
    },
    send: (data: any) => {
      res.send(data);
    },
    end: () => {
      res.end();
    },
  };
}

Copilot uses AI. Check for mistakes.
) {
// Set CORS headers for API access
res.setHeader("Access-Control-Allow-Credentials", "true");
res.setHeader("Access-Control-Allow-Origin", "*");
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setting Access-Control-Allow-Credentials: true with Access-Control-Allow-Origin: * is not allowed by the CORS specification and will cause browsers to reject the response. If you need credentials, specify an explicit origin instead of *, or remove the credentials header if not needed.

See: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS/Errors/CORSNotSupportingCredentials

Suggested change
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Origin", req.headers.origin || "");

Copilot uses AI. Check for mistakes.
try {
config = configSchema.parse(process.env);
} catch (error) {
console.error("❌ Invalid environment configuration:", error);
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to the project's coding guidelines, console.error should not be used. Use structured logging with the logger from logger.ts instead. However, in this specific case where the config is being initialized and the logger depends on the config, this creates a circular dependency. Consider restructuring to use a basic logger initialization that doesn't depend on config, or document this as an acceptable exception.

Copilot generated this review using guidance from repository custom instructions.
"scripts": {
"build": "vite build",
"dev": "node --watch src/index.ts",
"build": "tsc",
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The build command was changed from vite build to tsc, but vite is still listed as a devDependency and not removed. If Vite is no longer needed for the build process, consider removing it from dependencies to reduce package size. If it's still used elsewhere, this is fine.

Copilot uses AI. Check for mistakes.
Comment on lines +3 to +4
"buildCommand": "npm run build",
"outputDirectory": "dist",
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buildCommand and outputDirectory configuration may conflict with Vercel's automatic TypeScript compilation for serverless functions. Vercel automatically compiles TypeScript files in the api/ directory - you typically don't need these settings for serverless functions. Consider removing these lines or documenting why they're needed if there's a specific use case.

Suggested change
"buildCommand": "npm run build",
"outputDirectory": "dist",

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +9
import { logger } from "../logger.js";
import { getConfig } from "../config.js";
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports are pointing to incorrect paths. The logger and config modules are in the lib/ directory, not at the root level. These imports should be:

  • import { logger } from "../lib/logger.js";
  • import { getConfig } from "../lib/config.js";
Suggested change
import { logger } from "../logger.js";
import { getConfig } from "../config.js";
import { logger } from "../lib/logger.js";
import { getConfig } from "../lib/config.js";

Copilot uses AI. Check for mistakes.
Comment on lines +97 to +100
json: (data: any) => {
res.json(data);
},
send: (data: any) => {
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The use of any type for the data parameter in the json and send methods reduces type safety. Consider using unknown or a more specific type to maintain better type checking.

Suggested change
json: (data: any) => {
res.json(data);
},
send: (data: any) => {
json: (data: unknown) => {
res.json(data);
},
send: (data: unknown) => {

Copilot uses AI. Check for mistakes.
"pino": "^10.1.0",
"pino-pretty": "^13.1.2"
"pino-pretty": "^13.1.2",
"zod": "^3.24.1"
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] zod has been moved from devDependencies to dependencies. This is correct since it's now used at runtime in lib/config.ts for environment variable validation. However, it was previously only in devDependencies, which means the original src/config.ts also uses it at runtime. Consider also checking if zod should have been in dependencies all along in the original Express version.

Copilot uses AI. Check for mistakes.

### Option 2: GitHub Integration
1. Connect repository to Vercel
2. Select the `vercel-serverless` branch
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation references a vercel-serverless branch. Ensure this branch name is correct and will exist when this is merged, or update it to reference the appropriate branch (e.g., main if merging there).

Suggested change
2. Select the `vercel-serverless` branch
2. Select the `main` branch

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +17
"lint": "eslint src/ api/",
"lint:fix": "eslint src/ api/ --fix",
"format": "prettier --write '{src,api}/**/*.ts'",
"format:check": "prettier --check '{src,api}/**/*.ts'",
Copy link

Copilot AI Nov 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lint and format scripts should include the lib/ directory since new shared code has been added there. Update these scripts to:

  • "lint": "eslint src/ api/ lib/"
  • "lint:fix": "eslint src/ api/ lib/ --fix"
  • "format": "prettier --write '{src,api,lib}/**/*.ts'"
  • "format:check": "prettier --check '{src,api,lib}/**/*.ts'"
Suggested change
"lint": "eslint src/ api/",
"lint:fix": "eslint src/ api/ --fix",
"format": "prettier --write '{src,api}/**/*.ts'",
"format:check": "prettier --check '{src,api}/**/*.ts'",
"lint": "eslint src/ api/ lib/",
"lint:fix": "eslint src/ api/ lib/ --fix",
"format": "prettier --write '{src,api,lib}/**/*.ts'",
"format:check": "prettier --check '{src,api,lib}/**/*.ts'",

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants