MCP 서버 개발: Next.js와 Vercel로 SaaS 배포하기
MCP 서버 개발: Vercel에서 Next.js로 배포하기
2025-2026년에 SaaS 제품을 개발해왔다면, 모든 제품 관리자가 "AI 기능"을 원한다는 것을 눈치챘을 것입니다. 당연한 요구입니다. 하지만 정말 중요한 질문은 AI를 추가할지 여부가 아니라, AI 모델에게 애플리케이션의 데이터와 기능에 대한 구조화되고 안전한 접근을 제공하는 방법입니다. 이것이 바로 Model Context Protocol(MCP)이 해결하는 문제이며, Vercel에서 Next.js로 MCP 서버를 배포하는 것은 새로운 인프라를 구축하지 않고 빠르게 움직이려는 SaaS 팀을 위해 제가 본 가장 실용적인 패턴이 되었습니다.
지난 몇 개월 동안 저는 클라이언트를 위해 MCP 서버를 구축했습니다 -- 단순한 도구 제공 설정부터 복잡한 멀티테넌트 인증 흐름까지. 이 글에서는 Vercel에서 Next.js로 MCP 서버를 구축, 배포 및 확장하는 방법에 대해 배운 모든 것을 다룹니다.

목차
- MCP란 무엇이고 SaaS 팀이 관심을 가져야 하는 이유
- 아키텍처 개요: Vercel의 MCP
- Next.js MCP 서버 설정
- MCP 도구 및 리소스 구현
- 인증 및 멀티테넌시
- Vercel로 배포: 구성 및 주의사항
- 성능 최적화 및 확장
- 모니터링 및 관찰성
- 비용 분석: Vercel에서 MCP 서버 실행
- FAQ
MCP란 무엇이고 SaaS 팀이 관심을 가져야 하는 이유
Model Context Protocol(MCP)은 Anthropic이 원래 개발했고 이제 널리 채택되고 있는 개방형 표준으로, AI 모델이 외부 도구 및 데이터 소스와 상호 작용하는 방식을 정의합니다. 이를 AI를 위한 USB-C 포트로 생각해보세요. 어떤 AI 클라이언트든 MCP 호환 서버에 연결하는 데 사용할 수 있는 표준화된 인터페이스입니다.
MCP 이전에, Claude, GPT 또는 다른 모델이 SaaS 앱과 상호 작용하도록 하려면 각 AI 제공자에 대해 사용자 정의 통합을 구축해야 했습니다. OpenAI의 함수 호출은 Anthropic의 도구 사용과 달랐습니다. MCP가 이를 변경합니다. 하나의 서버를 구축하면 MCP 호환 클라이언트가 모두 사용할 수 있습니다.
SaaS 팀의 경우 이는 다음을 의미합니다:
- 사용자는 AI 통합을 기대합니다. 2026년 중반까지 약 68%의 B2B SaaS 사용자가 기본 도구와 함께 AI 어시스턴트를 사용한다고 보고합니다(Gartner, Q1 2026).
- MCP가 표준이 됩니다. Claude Desktop, Cursor, Windsurf, VS Code Copilot 및 수십 개의 다른 클라이언트는 이제 기본적으로 MCP를 지원합니다.
- 모든 AI 제공자에 대한 사용자 정의 통합을 구축하는 것보다 MCP 서버를 구축하는 것이 더 저렴합니다.
MCP vs. 기존 API 통합
| 측면 | 기존 API | MCP 서버 |
|---|---|---|
| 클라이언트 호환성 | 제공자당 일대일 | MCP 호환 클라이언트 |
| 검색 | 수동 문서 읽기 | 자동 도구/리소스 검색 |
| 인증 흐름 | 통합당 사용자 정의 | 표준화된 OAuth 2.1 / API 키 |
| 유지보수 부담 | 높음 (N개 통합) | 낮음 (1개 서버) |
| 실시간 데이터 | 폴링 또는 웹훅 | 서버 전송 이벤트 / 스트리밍 |
| 설정 시간 | 클라이언트당 며칠~주 | 서버용 시간, 클라이언트당 분 |
아키텍처 개요: Vercel의 MCP
여러 접근 방식을 반복한 후 내가 선택한 아키텍처는 다음과 같습니다:
┌─────────────────┐ ┌──────────────────────┐ ┌─────────────────┐
│ MCP Clients │ │ Vercel (Next.js) │ │ Your SaaS │
│ │ │ │ │ Backend │
│ - Claude │────▶│ /api/mcp (HTTP+SSE) │────▶│ - Database │
│ - Cursor │ │ /api/mcp/sse │ │ - APIs │
│ - Custom Apps │◀────│ /api/auth/[...mcp] │ │ - Services │
└─────────────────┘ └──────────────────────┘ └─────────────────┘
핵심 통찰: MCP 서버는 기존 API를 대체하지 않습니다. 그 앞에 번역 계층으로 앉습니다. MCP 서버는 기존 SaaS 기능에 매핑되지만 AI 모델이 검색하고 사용할 수 있는 형식의 도구와 리소스를 노출합니다.
Vercel에서 이것은 서버리스 함수로 실행됩니다. 최신 MCP 사양(v2025-12)은 전송으로 HTTP와 Server-Sent Events(SSE)를 지원하며, 이는 Next.js 라우트 핸들러의 Vercel 스트리밍 지원과 잘 작동합니다.
Next.js를 Vercel에서 사용하는 이유
모든 프레임워크 -- Express, Fastify, Hono, 뭐든지 -- 로 MCP 서버를 구축할 수 있습니다. 하지만 SaaS의 경우 Next.js on Vercel은 몇 가지 실질적인 이점을 제공합니다:
- 마케팅 사이트, 앱 및 MCP 서버가 한 저장소에 있습니다. 관리할 인프라가 적습니다.
- Edge 미들웨어가 요청이 MCP 엔드포인트에 도달하기 전에 인증을 처리합니다.
- Vercel의 스트리밍 지원은 SSE 기반 MCP 전송과 잘 작동합니다.
- 자동 확장 -- 서버에 대해 생각할 필요가 없습니다.
- 이미 Next.js를 실행 중이라면(통계적으로 그럴 가능성이 높음) 새로운 인프라가 없습니다.
Social Animal에서 많은 Next.js 개발을 수행했으며, 이 패턴은 가장 많이 요청되는 아키텍처 중 하나가 되었습니다.

Next.js MCP 서버 설정
이제 구축해봅시다. Next.js 15+와 App Router를 사용 중이라고 가정합니다.
의존성 설치
pnpm add @modelcontextprotocol/sdk zod
pnpm add -D @types/node
@modelcontextprotocol/sdk 패키지(2026년 초 기준 v1.12+)는 HTTP+SSE 전송에 필요한 모든 것을 포함합니다. 이전 버전은 stdio만 지원했으며, 이는 서버리스에서 작동하지 않습니다.
MCP 라우트 핸들러 생성
// app/api/mcp/route.ts
import { McpServer } from '@modelcontextprotocol/sdk/server';
import { httpTransport } from '@modelcontextprotocol/sdk/server/http';
import { z } from 'zod';
const server = new McpServer({
name: 'your-saas-mcp',
version: '1.0.0',
description: 'MCP server for YourSaaS platform',
});
// Register tools (we'll flesh these out next)
server.tool(
'get-projects',
'List all projects for the authenticated user',
{
status: z.enum(['active', 'archived', 'all']).optional().default('active'),
limit: z.number().min(1).max(100).optional().default(20),
},
async ({ status, limit }, context) => {
// Your actual business logic here
const projects = await fetchProjects(context.auth.userId, { status, limit });
return {
content: [
{
type: 'text',
text: JSON.stringify(projects, null, 2),
},
],
};
}
);
const handler = httpTransport(server, {
sessionManagement: true,
cors: {
origin: '*', // Lock this down in production
},
});
export const GET = handler;
export const POST = handler;
export const DELETE = handler;
스트리밍을 위한 SSE 엔드포인트
일부 MCP 클라이언트는 장기 실행 작업을 위해 SSE 전송을 선호합니다:
// app/api/mcp/sse/route.ts
import { sseTransport } from '@modelcontextprotocol/sdk/server/sse';
import { server } from '../mcp-server'; // Extract server config to shared module
export const GET = sseTransport(server, {
// Vercel has a 30-second timeout on Hobby, 300s on Pro
// For long-running tools, you'll need Pro plan minimum
keepAliveInterval: 15000,
});
MCP 도구 및 리소스 구현
이것이 실제 작업이 일어나는 곳입니다. MCP는 도구(AI가 취할 수 있는 행동)와 리소스(AI가 읽을 수 있는 데이터) 사이를 구분합니다. 이를 올바르게 하는 것은 MCP 서버가 AI 클라이언트에게 사랑받는지 아니면 어려움을 겪는지의 차이를 만듭니다.
좋은 도구 설계
내가 보는 가장 큰 실수: 너무 세분화되거나 너무 광범위한 도구. 50개의 작은 도구를 노출하면 AI 모델이 압도됩니다. 각각 20개의 매개변수를 취하는 3개의 메가 도구를 노출하면 모델이 실수를 합니다.
내 경험상: 사용자 의도당 하나의 도구. 사용자가 "최근 청구서를 보여줘"라고 말한다면 그것은 하나의 도구입니다. list-invoices + filter-invoices + format-invoices로 분할하지 마세요.
// Good: clear intent, reasonable parameters
server.tool(
'search-customers',
'Search for customers by name, email, or account ID. Returns matching customer profiles with recent activity.',
{
query: z.string().describe('Search term - can be name, email, or account ID'),
includeInactive: z.boolean().optional().default(false),
},
async ({ query, includeInactive }, context) => {
const customers = await customerService.search({
query,
tenantId: context.auth.tenantId,
includeInactive,
});
return {
content: [{
type: 'text',
text: JSON.stringify(customers.map(c => ({
id: c.id,
name: c.name,
email: c.email,
plan: c.plan,
mrr: c.mrr,
lastActive: c.lastActiveAt,
})), null, 2),
}],
};
}
);
리소스 노출
리소스는 AI 클라이언트가 컨텍스트로 가져올 수 있는 읽기 전용 데이터입니다. 모델이 참조할 수 있는 파일로 생각하세요:
server.resource(
'api-docs',
'Your SaaS API documentation',
'text/markdown',
async () => {
const docs = await fs.readFile('./docs/api-reference.md', 'utf-8');
return { content: docs };
}
);
// Dynamic resource with URI template
server.resourceTemplate(
'project/{projectId}/analytics',
'Analytics summary for a specific project',
'application/json',
async ({ projectId }, context) => {
const analytics = await analyticsService.getSummary(projectId, context.auth.tenantId);
return { content: JSON.stringify(analytics) };
}
);
인증 및 멀티테넌시
이것은 첫 번째 시도에서 모든 사람이 잘못 처리하는 부분입니다. MCP는 인증을 위해 OAuth 2.1을 지원하며, 멀티테넌트 SaaS를 구축하는 경우 이것이 절대 필요합니다.
MCP용 OAuth 2.1 흐름
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/api/mcp')) {
const authHeader = request.headers.get('authorization');
if (!authHeader?.startsWith('Bearer ')) {
return NextResponse.json(
{ error: 'Missing or invalid authorization header' },
{ status: 401 }
);
}
// Validate the token and inject tenant context
// This is where your existing auth system plugs in
}
}
export const config = {
matcher: '/api/mcp/:path*',
};
MCP 클라이언트가 필요한 OAuth 검색 엔드포인트의 경우:
// app/.well-known/oauth-authorization-server/route.ts
export function GET() {
return Response.json({
issuer: 'https://your-saas.com',
authorization_endpoint: 'https://your-saas.com/oauth/authorize',
token_endpoint: 'https://your-saas.com/api/oauth/token',
registration_endpoint: 'https://your-saas.com/api/oauth/register',
scopes_supported: ['mcp:read', 'mcp:write', 'mcp:admin'],
response_types_supported: ['code'],
code_challenge_methods_supported: ['S256'],
});
}
멀티테넌트 격리
모든 MCP 도구 호출은 인증된 테넌트로 범위가 지정되어야 합니다. 인증 컨텍스트가 모든 도구 핸들러에 자동으로 주입되는 패턴을 사용합니다:
const withTenant = (handler) => async (params, context) => {
const tenant = await resolveTenant(context.auth.token);
if (!tenant) throw new McpError('Invalid tenant');
return handler(params, { ...context, tenant });
};
테넌트 식별을 위해 도구 매개변수를 신뢰하지 마세요. 항상 인증 토큰에서 파생하세요.
Vercel로 배포: 구성 및 주의사항
vercel.json 구성
{
"functions": {
"app/api/mcp/route.ts": {
"maxDuration": 60
},
"app/api/mcp/sse/route.ts": {
"maxDuration": 300
}
},
"headers": [
{
"source": "/api/mcp/(.*)",
"headers": [
{ "key": "Cache-Control", "value": "no-store" }
]
}
]
}
아무도 말해주지 않는 주의사항
1. 함수 제한 시간. Vercel Hobby 플랜은 최대 30초입니다. Pro는 300초를 제공합니다. 느린 API를 쿼리하거나 데이터를 처리하는 MCP 도구의 경우 최소한 Pro가 필요합니다. 팀원당 $20/월이면 대부분의 SaaS 팀에게는 큰 문제가 아닙니다.
2. 콜드 스타트. 서버리스 콜드 스타트는 첫 요청에 200-800ms를 추가할 수 있습니다. MCP 클라이언트는 일반적으로 이를 잘 처리합니다 -- 50ms 이하의 응답을 기대하지 않습니다. 하지만 이것이 걱정된다면 Vercel의 cron을 사용하여 함수를 유지하세요.
3. SSE 및 스트리밍. Vercel은 스트리밍 응답을 지원하지만 CDN 계층에는 엣지 케이스가 있습니다. 모든 MCP 라우트에 Cache-Control: no-store를 설정하세요. 캐시된 SSE 응답으로 인해 클라이언트가 오래된 도구 목록을 수신할 때 제가 이것을 어려운 방식으로 배웠습니다.
4. 요청 본문 크기. Vercel은 서버리스 함수의 요청 본문을 4.5MB로 제한합니다. MCP 도구가 파일 업로드 또는 큰 페이로드를 처리하는 경우 대신 서명된 업로드 URL을 사용해야 합니다.
5. 환경 변수. MCP 서버의 공개 URL을 환경 변수로 설정하는 것을 잊지 마세요. 개발 중에는 ngrok 또는 Vercel의 미리보기 URL을 사용하지만, 프로덕션에서는 정규 도메인이어야 합니다.
# .env.production
MCP_SERVER_URL=https://your-saas.com/api/mcp
MCP_SERVER_NAME=your-saas-mcp
성능 최적화 및 확장
캐싱 전략
데이터가 자주 변경되지 않을 때 MCP 도구 응답을 캐시할 수 있습니다:
import { unstable_cache } from 'next/cache';
const getCachedAnalytics = unstable_cache(
async (tenantId: string, projectId: string) => {
return analyticsService.getSummary(tenantId, projectId);
},
['analytics-summary'],
{ revalidate: 300 } // 5 minutes
);
연결 풀링
MCP 도구가 데이터베이스에 접근하는 경우 연결 풀링을 사용하세요. Vercel에서 각 함수 호출은 자신의 실행 컨텍스트를 가지므로, 풀링 없이는 데이터베이스 연결이 빠르게 소진됩니다.
import { Pool } from '@neondatabase/serverless';
// Neon's serverless driver handles pooling automatically
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
Vercel의 데이터베이스 지원 MCP 도구에는 Neon 또는 PlanetScale을 권장합니다. 둘 다 서버리스 연결 모델을 잘 처리합니다.
벤치마크
Vercel Pro의 여러 프로덕션 MCP 배포에서 측정한 내용은 다음과 같습니다:
| 지표 | 콜드 스타트 | 따뜻함 | P99 |
|---|---|---|---|
| 단순 도구 (DB 없음) | 420ms | 45ms | 180ms |
| DB 지원 도구 (Neon) | 680ms | 95ms | 320ms |
| 외부 API 도구 | 850ms | 280ms | 1200ms |
| SSE 연결 설정 | 520ms | 60ms | 250ms |
| 도구 검색 (목록) | 380ms | 30ms | 120ms |
이 숫자들은 AI 클라이언트 상호 작용에 적합합니다. 모델은 어쨌든 응답을 생성하는 데 초를 소요합니다 -- MCP 서버가 병목이 되지 않을 것입니다.
모니터링 및 관찰성
프로덕션에서 무엇이 일어나고 있는지 알아야 합니다. MCP 서버는 "사용자"가 인간이 아닌 AI 모델이기 때문에 고유한 관찰성 요구사항이 있습니다.
추적할 사항
- 도구 호출 빈도 -- 모델이 실제로 어떤 도구를 사용하는가?
- 도구당 오류율 -- 특정 도구가 다른 것보다 더 자주 실패하는가?
- 토큰/테넌트 분포 -- 한 테넌트가 서버를 과도하게 사용하는가?
- 응답 페이로드 크기 -- 과도한 응답이 모델 컨텍스트 윈도우를 낭비하는가?
// Simple logging middleware for MCP tools
const withLogging = (toolName: string, handler: Function) => {
return async (params: any, context: any) => {
const start = performance.now();
try {
const result = await handler(params, context);
const duration = performance.now() - start;
console.log(JSON.stringify({
type: 'mcp_tool_invocation',
tool: toolName,
tenant: context.auth?.tenantId,
duration,
success: true,
responseSize: JSON.stringify(result).length,
}));
return result;
} catch (error) {
console.error(JSON.stringify({
type: 'mcp_tool_error',
tool: toolName,
tenant: context.auth?.tenantId,
error: error.message,
}));
throw error;
}
};
};
이 로그를 Axiom(Vercel의 통합 로깅), Datadog 또는 이미 사용 중인 것으로 전송하세요. Vercel의 내장 Log Drains는 이를 간단하게 만듭니다.
비용 분석: Vercel에서 MCP 서버 실행
이제 돈에 대해 이야기해봅시다. 2026년에 Vercel에서 MCP 서버를 실행하는 중간 규모 SaaS의 현실적인 비용 분석은 다음과 같습니다:
| 구성 요소 | Hobby | Pro | Enterprise |
|---|---|---|---|
| 기본 플랜 | $0/월 | 팀원당 $20/월 | 사용자 정의 |
| 함수 호출 (포함) | 100K | 1M | 사용자 정의 |
| 추가 호출 | N/A | 100만당 $0.60 | 협상 가능 |
| 대역폭 (포함) | 100GB | 1TB | 사용자 정의 |
| 최대 함수 지속 시간 | 30초 | 300초 | 900초 |
| Edge 미들웨어 | 포함됨 | 포함됨 | 포함됨 |
| 예상 월간 비용 (일일 10K MCP 요청) | 실행 불가능 | ~$25-40 | 사용자 정의 |
대부분의 SaaS 제품의 경우 Pro 플랜은 MCP 트래픽을 편안하게 처리합니다. 일일 10,000개의 MCP 도구 호출(상당히 활발함)에서 월간 약 300K 함수 실행 -- Pro의 포함된 할당 범위 내에 있습니다.
AWS에서 전용 MCP 서버를 실행하는 것과 비교하세요: 최소한 EC2 인스턴스($30-50/월), 로드 밸런서($18/월) 및 인프라 관리 시간이 필요합니다. Vercel은 운영상 단순성에서 승리합니다.
SaaS에 적합한 아키텍처를 평가 중이라면 도와드릴 수 있습니다. 가격 페이지를 확인하거나 직접 연락해주세요.
FAQ
Model Context Protocol(MCP)이란 무엇이며 함수 호출과 어떻게 다른가요?
MCP는 AI 모델을 외부 도구 및 데이터에 연결하기 위한 개방형 표준입니다. 제공자별 함수 호출(OpenAI의 함수 호출, Anthropic의 도구 사용)과 달리 MCP는 범용입니다. 하나의 MCP 서버를 구축하면 호환되는 클라이언트 -- Claude, Cursor, 사용자 정의 앱 -- 가 자동으로 도구를 검색하고 사용할 수 있습니다. 함수 호출은 각 AI 제공자에 대해 도구를 별도로 정의해야 합니다.
Vercel의 무료 Hobby 플랜에 MCP 서버를 배포할 수 있나요?
기술적으로 가능하지만 프로덕션에는 권장하지 않습니다. 30초 함수 제한 시간은 데이터베이스를 쿼리하거나 외부 API를 호출하는 MCP 도구에는 너무 제한적입니다. 또한 제한된 호출(월 100K)을 얻습니다. 팀원당 $20/월의 Pro 플랜이 실제 워크로드의 최소값입니다.
MCP 클라이언트와 내 SaaS 간의 인증을 어떻게 처리하나요?
MCP 사양은 OAuth 2.1을 지원합니다. MCP 클라이언트가 자동으로 검색하는 .well-known/oauth-authorization-server 엔드포인트를 노출합니다. 사용자가 Claude와 같은 AI 클라이언트를 통해 연결하면 표준 OAuth 흐름으로 리디렉션되고 권한을 부여하며 클라이언트는 범위가 지정된 액세스 토큰을 받습니다. 이 토큰은 모든 MCP 요청과 함께 전송됩니다.
MCP 도구와 MCP 리소스의 차이점은 무엇인가요?
도구는 행동입니다 -- AI가 취할 수 있는 것(프로젝트 생성, 이메일 전송, 쿼리 실행). 리소스는 데이터입니다 -- AI가 컨텍스트를 위해 읽을 수 있는 것(문서, 구성 파일, 분석 요약). 도구는 요청 시 호출되고, 리소스는 모델의 컨텍스트 윈도우에 로드됩니다. 행동을 위한 도구 설계, 참조 자료용 리소스.
내 서버는 몇 개의 MCP 도구를 노출해야 하나요?
내 경험상 대부분의 SaaS 제품의 경우 5-15개 도구가 최적입니다. 5개 미만이면 MCP 서버가 별로 유용하지 않습니다. 20개 이상이면 AI 모델이 도구 선택을 제대로 하지 못하기 시작합니다. 모든 CRUD 작업을 별도로 노출하는 대신 매개변수 옵션이 있는 단일 도구로 관련 작업을 그룹화합니다.
Next.js 이외의 프레임워크에서 작동하나요?
절대적으로. @modelcontextprotocol/sdk는 모든 Node.js 프레임워크와 함께 작동합니다. Hono, Express를 사용하거나 심지어 SSR 엔드포인트가 있는 Astro를 사용할 수 있습니다. Vercel의 Next.js는 내장 스트리밍 지원, edge 미들웨어 및 0 구성 배포 때문에 특히 편리한 조합일 뿐입니다. 다른 스택을 사용하는 경우, 당사의 headless CMS 개발 팀은 여러 프레임워크 전반에 걸쳐 MCP 서버를 구축했습니다.
개발 중 MCP 서버를 어떻게 테스트하나요?
MCP Inspector(공식 MCP 툴킷의 일부)가 최고의 친구입니다. 로컬 서버에 연결되어 도구를 호출하고, 리소스를 탐색하고, 응답을 대화식으로 디버그할 수 있습니다. 자동화된 테스트의 경우 MCP 서버를 프로세스에서 인스턴스화하고 도구를 프로그래밍 방식으로 호출하는 통합 테스트를 작성합니다 -- SDK는 HTTP 전송을 필요로 하지 않고 이를 지원합니다.
MCP 요청 중에 Vercel 함수 콜드 스타트가 발생하면 어떻게 되나요?
MCP 클라이언트는 지연에 대해 관용적으로 설계되었습니다 -- 일반적으로 초 단위의 응답을 기다리는 AI 모델을 기다립니다. 400-800ms 콜드 스타트는 실제로는 느낄 수 없습니다. 우려된다면 Vercel의 Pro 플랜을 사용하면 cron 기반 워밍을 구성할 수 있으며, SDK에는 일시적 오류에 대한 자동 재시도 로직이 포함됩니다. 6개월의 프로덕션 사용에서 콜드 스타트는 사용자가 보고한 문제가 된 적이 없습니다.