"Next.js MCP server"를 검색하면 같은 이름을 공유하는 서로 다른 두 가지 작업이 나옵니다. 어떤 사람들은 자신이 만드는 Next.js 앱 내부를 코딩 에이전트가 볼 수 있기를 원합니다. 페이지의 오류, 라우트, 빌드 문제 같은 것들입니다. 다른 사람들은 자신의 Next.js 앱을 MCP server로 바꿔서 Claude나 ChatGPT가 자기 사이트가 제공하는 도구를 호출할 수 있게 하려 합니다. 우리가 읽은 대부분의 페이지는 둘 중 하나만 다루면서 다른 하나를 흐릿하게 넘어가고, 많은 페이지가 두 번째 작업에 대해 여전히 라이브러리의 옛 버전을 보여줍니다.
이 가이드는 둘 다 다룹니다. 아래의 모든 명령과 코드 예제는 2026년 11월 14일에 macOS에서 Next.js 16.4.6, next-devtools-mcp 0.4.0, mcp-handler 2.4.0, @modelcontextprotocol/server 2.4.0, zod 4.6.7로 실행했습니다. 결과가 문서와 달랐던 부분은 그렇게 밝힙니다.
어떤 것이 필요하신가요?
| 원하는 작업 | 사용할 도구 | 실행 위치 | AI가 얻는 것 |
|---|---|---|---|
| Claude Code, Cursor, Codex가 작성 중인 앱을 디버깅하도록 하기 | 내장 /_next/mcp와 next-devtools-mcp |
내 머신, next dev에서만 |
실시간 오류, 라우트, 로그, 컴파일 검사, 버전에 맞는 문서 |
| AI 앱에 제품의 도구(검색, 주문, 문서) 제공하기 | mcp-handler로 만든 자체 라우트 |
앱이 배포된 곳 어디든 | 직접 작성한 도구만, 그 외에는 없음 |
| 둘 다 | 둘 다, 서로 충돌하지 않습니다 |
확실하지 않다면 첫 번째를 원하실 가능성이 높습니다. 명령 하나면 되고 코드는 필요하지 않습니다.
Part 1: 실행 중인 Next.js 앱에 코딩 에이전트 연결하기
Next.js 16이 /_next/mcp에서 노출하는 것
Next.js 16부터 next dev는 /_next/mcp에서 MCP 요청에 응답합니다. 기본적으로 켜져 있습니다. curl로 tools/list를 보내 보니 도구 아홉 개가 돌아왔습니다:
| 도구 | 반환하는 것 | 필요한 것 |
|---|---|---|
get_errors |
현재 오류: next.config 검증, 브라우저 런타임 오류, 소스 맵이 적용된 스택이 포함된 빌드 오류 |
런타임 오류의 경우 브라우저에 열린 페이지 |
get_logs |
브라우저 콘솔과 서버 출력이 담긴 dev 로그 파일 경로 | |
get_page_metadata |
현재 페이지 렌더링에 기여한 요소 | 브라우저에 열린 페이지 |
get_project_metadata |
프로젝트 경로와 dev 서버 URL | |
get_routes |
appRouter와 pagesRouter로 묶인 모든 라우트, 동적 세그먼트는 [param]으로 표시 |
|
get_server_action_by_id |
Server Action의 소스 파일과 export 이름 | |
get_compilation_issues |
모든 라우트의 resolve 오류, 누락된 모듈, transform 오류 | Turbopack |
compile_route |
요청하지 않고 라우트 하나를 컴파일 | Turbopack |
get_request_insights |
최근 요청 타임라인, fetch, 캐시 동작 | experimental.requestInsights |
서버는 스스로를 "Next.js MCP Server" 버전 0.2.0이라고 밝혔습니다. 두 페이지짜리 테스트 앱에서 get_routes는 우리가 가진 것과 정확히 같은 결과를 돌려줬습니다: /와 /api/mcp.
컴파일 도구가 조용한 승자입니다. 파일 세 개를 방금 수정한 에이전트는 코드가 반쯤 완성된 상태에서 전체 next build를 시작하는 대신, 실행 중인 dev 서버에 컴파일되는지 물어볼 수 있습니다.
의존하기 전에 알아 둘 제한이 두 가지 있습니다. 개발 환경에서만 존재합니다. 같은 앱을 빌드하고 next start를 실행하니 /_next/mcp가 404를 반환했습니다. 그리고 Next.js 16이 필요합니다. 15에서는 해당 경로에 아무것도 없습니다. npx @next/codemod@latest upgrade latest가 도구 자체가 권하는 업그레이드 방법입니다.
끄려면 next.config에서 experimental.mcpServer를 false로 설정합니다.
next-devtools-mcp 설치하기
에이전트는 /_next/mcp와 직접 통신하지 않습니다. next-devtools-mcp 패키지는 실행 중인 dev 서버를 찾아 호출을 전달하는 작은 stdio 서버입니다. 버전 0.4.0부터는 도구 네 개를 가진 얇은 커넥터입니다:
nextjs_index는 실행 중인 Next.js dev 서버와 각 서버가 노출하는 도구를 나열합니다.nextjs_call은port와toolName을 지정해 서버에서 해당 도구 중 하나를 실행합니다.nextjs_docs는 설치된 Next.js 안의node_modules/next/dist/docs/에 포함된 문서를 에이전트에 안내하므로 답변이 버전과 일치합니다.browser_eval은 실제 브라우저에서 페이지를 확인할 수 있도록agent-browserCLI를 에이전트에 안내합니다.
Node.js 20.19 이상이 필요합니다. 클라이언트를 고르세요:
Claude Code
claude mcp add next-devtools npx next-devtools-mcp@latest
Codex
codex mcp add next-devtools -- npx next-devtools-mcp@latest
Windows 11에서 Codex는 README에 따르면 .codex/config.toml에 env = { SystemRoot="C:\\Windows", PROGRAMFILES="C:\\Program Files" }와 startup_timeout_ms = 20_000도 필요합니다.
Gemini CLI
gemini mcp add next-devtools npx next-devtools-mcp@latest
현재 프로젝트 대신 모든 프로젝트에 설치하려면 -s user를 추가합니다.
VS Code with Copilot
code --add-mcp '{"name":"next-devtools","command":"npx","args":["-y","next-devtools-mcp@latest"]}'
Cursor 또는 JSON 파일을 읽는 모든 클라이언트
{
"mcpServers": {
"next-devtools": {
"command": "npx",
"args": ["-y", "next-devtools-mcp@latest"]
}
}
}
프로젝트 루트의 .mcp.json에 넣으면 git을 통해 팀과 공유할 수 있고, Cursor에서만 쓰려면 ~/.cursor/mcp.json에 넣습니다.
모든 에이전트에 한 번에
npx add-mcp next-devtools-mcp@latest
add-mcp는 머신에서 찾은 모든 코딩 에이전트에 항목을 기록합니다.
동작 확인하기
dev 서버를 먼저 시작합니다. 그다음 작업 중인 페이지를 브라우저에서 엽니다. get_errors와 get_page_metadata는 실행 중인 브라우저 세션에서 읽기 때문입니다. 이제 실행 중인 앱만 아는 것을 에이전트에게 물어보세요:
- "지금 앱에 어떤 오류가 있나요?"
- "라우트를 나열하고 어떤 것이 동적인지 알려 주세요."
- "
/blog/[slug]는 컴파일되나요?"
Claude Code에서는 nextjs_index를 호출한 뒤 toolName: "get_errors"로 nextjs_call을 호출하는 것을 볼 수 있습니다. 에이전트가 대신 파일을 읽어서 답한다면 연결되지 않은 것입니다.
에이전트 없이 엔드포인트를 확인할 수도 있습니다:
curl -s -X POST http://localhost:3000/_next/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
도구 목록이 나오면 Next.js는 정상이고, 문제가 있다면 에이전트와 next-devtools-mcp 사이에 있는 것입니다.
우리가 겪은 것: 다른 포트의 dev 서버
테스트 앱은 3000 포트가 이미 사용 중이라 3417 포트에서 실행됐습니다. nextjs_index가 이를 찾지 못했습니다. 대신 마침 3000에서 실행 중이던 다른 프로젝트를 찾았습니다.
패키지 소스를 읽어 보니 이유가 나왔습니다. macOS와 Linux에서 next-devtools-mcp 0.4.0은 먼저 이름이 정확히 next-server인 프로세스를 찾습니다. 우리 Mac에서는 프로세스 이름이 next-server (v16.4.6)이라 이 매칭이 실패했고, 패키지는 3000부터 3010까지 포트를 탐색하는 방식으로 넘어갔습니다. 다른 포트의 dev 서버는 보이지 않습니다.
해결은 간단합니다. nextjs_call은 포트를 직접 받으며, 3417을 넘기자 바로 동작했습니다:
{ "port": 3417, "toolName": "get_routes" }
따라서 프로젝트를 둘 이상 실행하거나 사용자 지정 포트를 쓴다면 에이전트에게 한 번 알려 주세요: "dev 서버는 3417 포트에 있습니다." 그러면 에이전트가 해당 포트로 도구를 호출합니다. 프로젝트를 3000에서 3010 사이에 두는 것도 문제를 피하는 방법입니다.
Part 2: Next.js 앱 안에 직접 MCP server 만들기
이것은 다른 의미입니다. 여러분의 Next.js 앱이 Claude, Cursor, ChatGPT가 URL로 접속할 수 있는 대상이 됩니다. 전형적인 도구는 콘텐츠 검색, 데이터베이스 조회, 또는 지원 티켓 생성 같은 액션입니다.
Vercel의 mcp-handler가 프로토콜 처리를 담당합니다. 서버 정의를 평범한 (Request) => Response 함수로 바꿔 주는데, 이는 Next.js Route Handler가 내보내는 것과 정확히 같습니다.
버전 2에서 많이 바뀌었습니다
대부분의 튜토리얼은 아직 Redis, SSE 엔드포인트, [transport] 동적 라우트를 쓰는 버전 1을 보여 줍니다. 버전 2는 더 단순하고, 예전 스니펫은 버전 2에서 동작하지 않습니다:
| mcp-handler 1.x | mcp-handler 2.x | |
|---|---|---|
| SDK 패키지 | @modelcontextprotocol/sdk 1.x |
@modelcontextprotocol/server 2.x |
| zod | 3 | 4.2 이상 |
| Node.js | 20 이상 | |
| 전송 방식 | Streamable HTTP와 SSE | Streamable HTTP만, 무상태 |
| Redis | SSE에 필요 | 사용하지 않음 |
| 라우팅 옵션 | basePath, maxDuration, redisUrl 등 |
제거됨: 원하는 라우트에 핸들러를 마운트 |
1.x에 머무른다면 @modelcontextprotocol/sdk를 1.26.0 이상으로 유지하세요. mcp-handler README는 그 이전 SDK 버전에 보안 취약점이 있다고 표시합니다.
설치
npm install mcp-handler@^2 @modelcontextprotocol/server@^2 zod@^4
라우트
app/api/mcp/route.ts를 만듭니다. 이 예제는 AI 클라이언트에 블로그 글 검색을 제공합니다. 배열을 여러분의 데이터베이스나 CMS로 바꾸세요.
import { createMcpHandler } from "mcp-handler";
import { z } from "zod";
const posts = [
{ slug: "edge-caching", title: "Caching at the edge", body: "How we cache HTML at the edge for 10 minutes." },
{ slug: "image-sizes", title: "Picking image sizes", body: "Three widths cover most layouts." },
];
const handler = createMcpHandler(
(server) => {
server.registerTool(
"search_posts",
{
title: "Search posts",
description: "Find blog posts whose title or body contains the query.",
inputSchema: z.object({ query: z.string().min(2) }),
},
async ({ query }) => {
const q = query.toLowerCase();
const hits = posts.filter((p) => `${p.title} ${p.body}`.toLowerCase().includes(q));
return {
content: [{ type: "text", text: hits.length ? hits.map((p) => `${p.title} (/blog/${p.slug})`).join("\n") : "No posts found." }],
};
},
);
},
{ serverInfo: { name: "my-blog", version: "1.0.0" } },
);
export { handler as GET, handler as POST };
/api/mcp는 관례일 뿐입니다. 핸들러는 경로를 보지 않으므로 app/mcp/route.ts도 똑같이 동작합니다. 어떤 라우트를 선택하든 클라이언트에는 그 라우트의 전체 URL을 알려 주세요.
serverInfo를 설정하세요. 설정하지 않으면 우리 서버는 클라이언트에 "mcp-typescript server on vercel"로 자신을 소개했고, 사용자는 클라이언트의 서버 목록에서 이것을 보게 됩니다.
curl로 테스트하기
npm run dev가 실행 중인 상태에서 다음 세 요청이면 전체 왕복이 됩니다. 위 라우트에 대해 실행했습니다:
curl -s -X POST http://localhost:3000/api/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'
응답은 "serverInfo":{"name":"my-blog","version":"1.0.0"}가 담긴 서버 전송 이벤트 하나로 돌아왔습니다. 본문을 {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}로 바꾸면 JSON Schema와 함께 search_posts를 볼 수 있고, 다음 요청으로 도구를 실행할 수 있습니다:
{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"search_posts","arguments":{"query":"edge"}}}
Caching at the edge (/blog/edge-caching)를 받았습니다. 라우트에 평범한 GET을 보내면 405가 돌아왔는데, 이는 예상된 결과입니다. 버전 2는 무상태이고 열어 둘 장기 스트림이 없습니다.
curl 대신 클릭으로 확인하려면 MCP Inspector도 쓸 수 있습니다: npx @modelcontextprotocol/inspector를 실행한 뒤 Streamable HTTP 전송으로 http://localhost:3000/api/mcp에 연결하세요.
클라이언트 연결하기
Streamable HTTP를 지원하는 클라이언트는 URL로 연결합니다.
Claude Code
claude mcp add --transport http my-blog http://localhost:3000/api/mcp
Cursor (~/.cursor/mcp.json)
{
"mcpServers": {
"my-blog": { "url": "http://localhost:3000/api/mcp" }
}
}
VS Code (.vscode/mcp.json)
{
"servers": {
"my-blog": { "type": "http", "url": "http://localhost:3000/api/mcp" }
}
}
Claude Desktop은 Settings › Connectors에서 원격 서버를 읽습니다. claude_desktop_config.json에 로컬 URL을 넣으려면 mcp-remote로 브리지하세요:
{
"mcpServers": {
"my-blog": { "command": "npx", "args": ["-y", "mcp-remote", "http://localhost:3000/api/mcp"] }
}
}
배포한 뒤에는 localhost URL을 실제 URL로 바꾸세요. 예를 들어 https://your-app.vercel.app/api/mcp입니다. 서버를 config builder에 등록하면 15개 클라이언트용 스니펫을 만들어 줍니다.
배포 전에 보호하기
배포된 라우트는 공개됩니다. URL을 찾은 사람은 누구나 여러분의 도구를 호출할 수 있으므로, 인증은 배포 후가 아니라 첫 배포 전에 추가하세요.
withMcpAuth가 핸들러를 감쌉니다. 환경 변수에서 가져온 공유 토큰 하나를 쓰는, 우리가 테스트한 버전입니다:
import { createMcpHandler, withMcpAuth } from "mcp-handler";
// createMcpHandler(...) as above
const verifyToken = async (_req: Request, bearerToken?: string) => {
if (!bearerToken || bearerToken !== process.env.MCP_TOKEN) return undefined;
return { token: bearerToken, clientId: "team", scopes: ["posts:read"] };
};
const authed = withMcpAuth(handler, verifyToken, { required: true });
export { authed as GET, authed as POST };
required: true가 중요합니다. 이것이 없으면 유효한 토큰이 없는 요청도 그대로 통과합니다. 이것이 있으면 인증 없는 호출은 다음을 받았습니다:
HTTP/1.1 401 Unauthorized
www-authenticate: Bearer error="invalid_token", error_description="No authorization provided", resource_metadata="http://localhost:3417/.well-known/oauth-protected-resource"
그리고 Authorization: Bearer <token>을 붙인 같은 호출은 200을 받았습니다.
이 resource_metadata 링크가 OAuth를 지원하는 클라이언트가 로그인을 찾는 경로입니다. app/.well-known/oauth-protected-resource/route.ts에서 제공하세요:
import { metadataCorsOptionsRequestHandler, protectedResourceHandler } from "mcp-handler";
const handler = protectedResourceHandler({ authServerUrls: ["https://auth.example.com"] });
const corsHandler = metadataCorsOptionsRequestHandler();
export { handler as GET, corsHandler as OPTIONS };
응답은 {"resource":"http://localhost:3417","authorization_servers":["https://auth.example.com"]}였습니다. authServerUrls를 실제 ID 공급자로 지정하세요. 실제 사용자를 위해서는 공유 토큰을 그 공급자 토큰 검증으로 바꾸고, 라우트마다 requiredScopes를 확인하세요. 현재 명세 개정판인 2026-07-28은 클라이언트 등록을 Dynamic Client Registration에서 Client ID Metadata Documents로 옮깁니다. 이 작업은 여러분의 인증 서버에서 이루어지며, Next.js 라우트는 바뀌지 않습니다.
도구 안에서는 검증된 신원이 ctx.http?.authInfo에 있으므로, 도구가 호출자의 계정으로 결과를 필터링할 수 있습니다.
문제와 해결 방법
| 보이는 증상 | 원인 | 해결 |
|---|---|---|
next-devtools-mcp에서 [error] No server info found |
Next.js 16 dev 서버가 실행 중이 아니거나 3000~3010 포트 범위를 벗어남 | npm run dev를 실행합니다. 다른 포트를 쓴다면 nextjs_call에 해당 포트를 에이전트에 알려 줍니다 |
next-devtools-mcp/dist에 대한 ERR_MODULE_NOT_FOUND |
오래된 npx 캐시 | npx 캐시를 지우고 클라이언트를 재시작합니다 |
/_next/mcp에서 404 |
Next.js 15 이하, next start, 또는 experimental.mcpServer: false |
업그레이드하거나 next dev를 사용하거나 플래그를 제거합니다 |
| 에이전트가 파일에서만 답하고 도구를 호출하지 않음 | 클라이언트가 서버를 로드하지 않음 | 클라이언트를 재시작하고 MCP 목록을 확인합니다. Claude Code에서는 /mcp를 실행합니다 |
GET /api/mcp에서 405 |
mcp-handler 2에서는 정상 | 고칠 것이 없습니다. 클라이언트는 POST를 사용합니다 |
WWW-Authenticate 헤더와 함께 401 |
required: true인 withMcpAuth에 토큰이 없음 |
Authorization: Bearer ...를 보내거나 클라이언트의 OAuth 흐름으로 로그인합니다 |
예전 튜토리얼 코드가 redisUrl 또는 basePath에서 실패 |
mcp-handler 1용으로 작성된 코드 | 해당 옵션을 제거하고 원하는 위치에 라우트를 마운트합니다 |
| 새 프로젝트에서 "The module was automatically converted to an EcmaScript module" | npm init이 package.json에 "type": "commonjs"를 추가함 |
type 필드를 삭제합니다. Next.js가 모듈을 직접 처리합니다 |
마지막 항목은 빈 테스트 프로젝트에서 5분을 잡아먹었습니다. create-next-app에는 이 문제가 없습니다.
디렉터리에 있는 Next.js MCP server
2026년 11월 14일 기준 HostedMCP 카탈로그에는 이름에 Next.js가 들어간 활성 server가 19개 있었고, 그중 4개가 인덱스 규칙을 통과했습니다. 이 가이드에서 중요한 두 가지는 다음과 같습니다.
| Server | Stars | 검사 점수 | 설명 |
|---|---|---|---|
| vercel/next-devtools-mcp | 836 | 89 | Part 1의 커넥터 |
| vercel-labs/mcp-handler | 699 | 86 | Part 2의 라이브러리 |
Star 수는 마지막 새로고침 시점의 GitHub 기준이며, 검사 점수는 저장소, 라이선스, 유지보수, 공급망, 도구 설명을 자동으로 검토한 결과로, 근거는 각 server의 검사 보고서에서 확인할 수 있습니다. 나머지 17개 중 대부분은 스타터 템플릿이거나 star가 적은 단일 목적 도구입니다. 설치하기 전에 같은 방식으로 확인하시기 바랍니다.
보안 참고 사항
개발 엔드포인트는 개발 전용이지만 비공개는 아닙니다. next dev가 실행되는 동안 해당 포트에 접근할 수 있는 모든 프로세스가 프로젝트 경로, 라우트, 로그 위치를 읽을 수 있습니다. 환경에 시크릿이 들어 있는 동안에는 개발 서버를 공유 네트워크 주소에 바인딩하지 마십시오.
직접 만든 라우트는 배포하는 날부터 공개 API입니다. 모든 도구를 엔드포인트처럼 다루십시오. 스키마로 입력을 검증하고, 호출자가 볼 수 있는 것만 반환하며, 도구 설명에 시크릿이나 내부 URL을 절대 넣지 마십시오. 모델이 그 설명을 읽고, 여러분의 도구를 나열하는 사람도 읽기 때문입니다. MCP 보안 가이드에서 위험 요소를 순서대로 다룹니다.
다음 단계
라우트 뒤에 있는 프로토콜 세부 사항이 궁금하다면 MCP server를 만드는 방법에서 순수 TypeScript와 Python으로 하나를 만들어 봅니다. 서버가 준비되면 저장소를 제출하세요. 그러면 HostedMCP가 각 클라이언트용 설치 스니펫과 함께 목록에 올리고 검사를 실행합니다.