새로운 MCP SDK를 사용하여 TypeScript로 MCP v2 서버 구축하기
요약
새로운 MCP v2 SDK를 사용하여 TypeScript 기반의 stateless 서버를 구축하는 실습 가이드입니다. v1 대비 변경된 패키지 구조, 세션 관리 방식의 변화, 그리고 마이그레이션 방법을 상세히 다룹니다.
핵심 포인트
- npx 명령어를 통한 MCP v2 서버 스캐폴딩 방법 제공
- v1과 v2의 주요 차이점 및 세션 관리 코드 제거 사항 설명
- 새로운 패키지 레이아웃 및 도구 스키마 변경 사항 안내
- Node.js 22.19 이상 버전 사용 권장 및 Inspector 관련 주의사항
TypeScript로 상태가 없는 (stateless) MCP v2 서버를 구축해 보세요. 새로운 @modelcontextprotocol/server 패키지, createMcpHandler, Standard Schema 도구, 그리고 세션 핸들링 (session handling)을 대체하는 요소들을 다루는 MCP TypeScript v2 SDK 실습 가이드입니다.
서론 (Introduction)
MCP v2가 확정되었습니다. 프로토콜 개정안 2026-07-28이 출시되었으며, 이에 맞춰 TypeScript SDK도 함께 출시되었습니다: @modelcontextprotocol/server 2.0.0 및 관련 패키지들이 npm에 안정 버전 (stable releases)으로 올라와 있습니다.
지금 바로 v2 서버를 실행하고 싶다면, 이 가이드가 도움을 줄 것입니다. 가장 빠른 방법은 단 하나의 명령어를 사용하는 것입니다:
npx @agentailor/create-mcp-server@0.7.0 --name=fetch-mcp-server
이 명령어는 작동 가능한 MCP v2 서버의 스캐폴딩 (scaffolding)을 수행합니다. 이 글의 나머지 부분은 v1과 v2의 차이점을 학습 도구로 사용하여, 방금 그 명령어가 여러분을 위해 무엇을 수행했는지 설명합니다. 변경된 내용의 대부분은 여러분이 더 이상 직접 작성할 필요가 없는 내부 구조 (plumbing)에 관한 것입니다.
학습 내용:
- v2 서버를 스캐폴딩하는 방법과 생성된 코드가 하는 역할
- 왜 v1의 세션 관리 (session-management) 코드가 완전히 사라졌는지
- 새로운 패키지 레이아웃 및 도구 스키마 (tool schemas)의 변경 사항
- 기존 v1 서버를 마이그레이션 (migrate)하는 방법
사전 요구 사항:
- Node.js 22.19 이상 (아래 노트를 참조하세요; 이는 v1 요구 사항보다 높습니다)
- 기본적인 TypeScript 숙련도
MCP가 아예 처음이신가요? MCP는 AI 모델이 외부 도구를 발견하고 호출할 수 있게 해주는 개방형 표준으로, 세 가지 기능 유형을 가집니다: 도구 (tools) (동작), 리소스 (resources) (읽기 가능한 데이터), 그리고 프롬프트 (prompts) (템플릿)입니다. 만약 이 내용들이 생소하다면, 먼저 5분 만에 첫 MCP 서버 만들기를 읽어보세요. 해당 글은 v1을 기준으로 초보자 수준에서 개념과 전송 (transports) 방식을 다루고 있으며, 그곳의 모든 개념적 내용은 여전히 유효합니다. 이 가이드는 여러분이 해당 기초 지식을 갖추고 있다고 가정하며, 전적으로 v2에 초점을 맞춥니다.
[!NOTE]
Node 버전 요구 사항이 올라갔습니다. v2 패키지 자체가 이를 강제하는 것은 아닙니다. 요구 사항은 스캐폴드(scaffold)가 개발 의존성(dev dependency)으로 설치하는@modelcontextprotocol/inspectorv2에서 발생하며, 이는 Node >= 22.19.0을 필요로 합니다. Inspector는 버전을 올릴 만한 가치가 있습니다. v2는 v1보다 더 현대적이고 직관적인 UI를 갖춘 진정한 재설계(redesign)이기 때문입니다. 만약 Node 20을 사용 중이라면,npm install시 엔진 체크(engine check)에서 실패합니다. Node를 업그레이드하거나, 다른 방식으로 테스트할 계획이라면 Inspector를 제외하십시오.
스캐폴드 생성하기 (Scaffold It)
--name 옵션을 전달하면 CLI가 비대화형(non-interactive) 모드로 동작합니다. 즉, 다른 모든 옵션을 기본값으로 가져와 질문 과정 없이 즉시 프로젝트를 생성합니다.
npx @agentailor/create-mcp-server@0.7.0 --name=fetch-mcp-server
cd fetch-mcp-server
npm install
...
이제 http://localhost:3000/mcp에서 v2 서버를 사용할 수 있습니다.
옵션을 단계별로 안내받고 싶다면 --name 플래그를 생략하십시오:
npx @agentailor/create-mcp-server@0.7.0
여기서는 기본값을 사용하는 것이 좋지만, 이해해둘 만한 세 가지 옵션이 있습니다:
--framework=sdk(기본값)는 v2를 생성합니다. 대신--framework=fastmcp를 전달하면 v1 서버를 얻게 됩니다. FastMCP는 아직 마이그레이션되지 않았으며, 템플릿은 작동하지 않는 것을 제공하기보다 의도적으로 v1을 유지하고 있습니다.--template은 더 이상 SDK 프로젝트에 아무런 영향을 주지 않습니다. v1에서는 Stateless 또는 Stateful을 선택할 수 있었고, 이들은 실제로 다른 코드를 생성했습니다. v2에서는 그러한 구분이 존재하지 않으므로, 두 값 모두 동일한 서버를 생성합니다. 기존 스크립트가 깨지지 않도록 플래그는 여전히 허용됩니다.--stdio는 전송 방식(transport)을 전환합니다. 기본값은 스트리밍 가능한 HTTP이며, 이 가이드가 구축하는 방식입니다. 대신 로컬 하위 프로세스(subprocess)로 실행되는 서버를 원한다면--stdio를 사용하십시오.
@0.7.0으로 버전을 고정(pin)함으로써 생성된 코드가 여기서 읽는 내용과 일치하도록 유지합니다. v0.7.0은 v2를 배포하는 첫 번째 릴리스이며, v0.6.2는 v1을 배포하는 마지막 릴리스입니다.
CLI가 생성한 것과 변경된 점
세션 플러밍(session plumbing)이 사라졌습니다
이것은 헤드라인이며, 삭제로 보는 것이 가장 좋습니다.
agentailor/fetch-mcp-server에서 가져온 실제 v1 진입점(entrypoint)입니다. 이는 v1 퀵스타트에서 구축된 서버이며, 상태를 유지하는(stateful) 서버이므로 전체 세션 장치(session apparatus)를 포함합니다:
const app = createMcpExpressApp()
// 세션 ID별 트랜스포트를 저장하는 Map
...
이는 대략 150줄 분량입니다. v2에서는 사실상 모든 것이 삭제되었습니다:
import { type Request, type Response } from 'express'
import { createMcpHandler } from '@modelcontextprotocol/server'
import { createMcpExpressApp } from '@modelcontextprotocol/express'
...
아래의 startServer 및 SIGINT 보일러플레이트는 v1과 변경된 부분이 없으므로 생략합니다. allowedHosts는 v2 호스트 검증 가드(host-validation guard)입니다. 기본적으로 localhost가 허용되며, 프로덕션 호스트 이름은 ALLOWED_HOSTS 환경 변수를 통해 추가해야 합니다.
v1 파일이 수동으로 처리했던 모든 것이 이제 createMcpHandler 내부에 있습니다. 세션 맵도 없고, isInitializeRequest 분기(branching)도 없습니다. SSE를 위한 별도의 GET 라우트도 없고, 세션을 종료하기 위한 DELETE 라우트도 없으며, SIGINT 루프가 트랜스포트를 닫는 것도 없고, 수동으로 작성된 JSON-RPC 오류 본문도 없습니다. 모든 메서드를 포괄하는 하나의 app.all이 사용되는데, 이는 핸들러 내부에서 디스패치(dispatches)하기 때문입니다.
핵심 라인은 팩토리(factory)입니다. createMcpHandler(() => getServer())는 getServer()를 요청당 한 번씩 호출하므로, 신선한 McpServer가 모든 호출에 서비스를 제공하며 어떤 인스턴스도 요청 간 상태를 유지하지 않습니다. 이것이 구체적으로
v1은 깊은 서브패스 임포트 (deep subpath imports)를 포함한 하나의 패키지를 출시했습니다. v2는 이를 집중된 패키지들로 분리합니다:
| v1 | v2 |
|---|---|
@modelcontextprotocol/sdk/server/mcp.js | @modelcontextprotocol/server |
| ... |
생성된 package.json의 런타임 의존성 (runtime dependencies):
{
"dependencies": {
"@modelcontextprotocol/server": "^2.0.0",
...
두 가지 참고 사항이 있습니다. **hono는 @modelcontextprotocol/node의 선택적 피어 의존성 (optional peer dependency)**이므로, 템플릿 파일에서 이를 임포트하지 않더라도 나타납니다. 그리고 기존의 @modelcontextprotocol/sdk 패키지는 v1 서버를 위해 1.x 버전에 머물러 있는 별개의 패키지입니다. 이를 2.x로 고정하려고 시도하지 마세요. v2 패키지는 위에 나열된 것들입니다.
도구 스키마 (Tool schemas)가 Standard Schema로 변경됨
v1에서는 inputSchema가 Zod 검증기 (validators)의 가공되지 않은 객체 (raw object)를 받았습니다. v2에서는 Standard Schema 객체를 받으며, 이는 전체 z.object({ ... })를 의미합니다:
// v1
inputSchema: {
url: z.url().describe('The URL to fetch.'),
...
동일한 변경 사항이 registerPrompt의 argsSchema에도 적용됩니다.
래핑된 (wrapped) 형태가 SDK에서 문서화하고 스캐폴드 (scaffold)가 생성하는 방식이므로, 새로운 코드는 그 방식에 맞춰 작성하세요. 하지만 실제로 2.0.0 버전은 베타 릴리스보다 더 관대합니다. 가공되지 않은 형태 (raw shape)도 여전히 등록되고 작동합니다. 저는 zod 4.4.3을 사용하여 2.0.0에서 이를 확인했습니다. 가공되지 않은 형태로 선언된 도구는 올바르게 생성된 JSON 스키마 (JSON Schema)와 함께 tools/list에 나타나며 정상적으로 실행됩니다.
이 점은 이전의 마이그레이션 노트를 읽고 있다면 중요합니다. 베타 기간 동안에는 가공되지 않은 형태를 사용할 경우 오류 없이 도구가 tools/list에서 사라진다는 보고가 있었으며, 해당 동작에 반대하는 조언들을 발견할 수 있을 것입니다. 안정화된 릴리스에서는 이 현상이 재현되지 않습니다. z.object({ ... })로 마이그레이션하는 이유는 도구가 사라지기 때문이 아니라, 이것이 문서화된 방식이며 향후 호환성 (forward-compatible)을 보장하는 형태이기 때문입니다.
tools/list가 비어 있나요? zod 버전을 확인하세요
만약 도구가 사라지거나 호출이 완전히 실패한다면, 일반적인 원인은 zod 3입니다. v2는 스키마를 JSON 스키마로 변환하기 위해 zod >= 4.2.0이 필요합니다.
다행인 점은 이것이 베타 버전 때처럼 소리 없이 실패하지 않는다는 것입니다. 2.0.0 버전에서는 다음과 같이 명시적이고 자기 진단적인 에러를 제공합니다:
ProtocolError: Schema appears to be from zod 3, which the SDK cannot convert
to JSON Schema. Upgrade to zod >=4.2.0, or wrap your JSON Schema with
fromJsonSchema().
해결 방법은 메시지에서 알려주는 것과 같습니다:
npm install zod@^4.4.3
참고할 사항: zod 3.25는 ~standard 인터페이스를 구현하므로, 검사 시 스키마 객체가 유효해 보이며 registerTool이 불만 없이 이를 수락합니다. 실패는 SDK가 도구 목록(tool listing)을 위한 JSON 스키마(JSON Schema)를 생성하려고 시도할 때 나중에 나타납니다. 만약 이 문제를 디버깅 중이라면, 버전 번호가 스키마 객체 자체에서 볼 수 있는 그 어떤 것보다 더 신뢰할 수 있는 신호입니다.
Capabilities 인자 제거
v1 서버는 두 번째 생성자 인자(constructor argument)에서 capabilities를 선언했습니다. v2에서는 logging, sampling, roots가 지원 중단(deprecated)되었으므로, 해당 인자는 사라졌습니다:
// v1
const server = new McpServer(
{ name: 'my-server', version: '1.0.0' },
...
만약 프로토콜 수준의 logging에 의존했다면, stdio 서버의 경우 stderr가, 프로덕션 관찰성(observability)의 경우 OpenTelemetry가 그 대체재입니다. MCP v2: What's Changing and Why에서 세 가지 지원 중단 사항에 대한 배경 설명을 다루고 있습니다.
Request context: extra가 ctx로 변경됨
도구 핸들러(Tool handlers)는 두 번째 파라미터로 컨텍스트(context) 객체를 받습니다. v1에서는 extra였으나 이제는 ctx로 변경되었으며, 전송(transport) 관련 필드가 프로토콜 관련 필드와 분리되도록 재구성되었습니다:
ctx.mcpReq.signal— 요청에 대한 중단 신호(abort signal)ctx.http?.authInfo— 인증 정보(auth info), stdio 서버는 뒤에 HTTP 요청이 없으므로 선택 사항(optional)입니다.
?. 부분이 중요합니다. 서버가 HTTP 전용이 아니라면 http를 방어적으로 읽으세요.
Fetch 도구 구축하기
실제 도구를 작성해 봅시다: URL을 가져와서 HTML을 마크다운 (markdown)으로 변환하는 fetch 도구입니다. 이 예제는 v1 퀵스타트와 의도적으로 동일하게 구성하였으므로, 두 버전을 직접 비교해 볼 수 있습니다.
HTML 변환기를 설치하세요:
npm install node-html-markdown
src/server.ts를 교체하세요:
import type { CallToolResult, GetPromptResult } from '@modelcontextprotocol/server'
import { McpServer } from '@modelcontextprotocol/server'
import { z } from 'zod'
...
이를 v1 버전과 비교해 보면 도구 로직은 동일합니다. 오직 세 가지만 변경되었습니다: 임포트 (import) 소스, z.object({ ... }) 래퍼 (wrapper), 그리고 삭제된 capabilities 인자입니다. 여러분의 비즈니스 로직은 프로토콜이 상태가 없는 (stateless) 방식으로 변경되었다는 사실에 영향을 받지 않으며, 이것이 바로 핵심입니다.
MCP Inspector로 테스트하기
서버를 시작한 다음, 두 번째 터미널에서 다음을 실행하세요:
npm run inspect
전송 방식 (transport)을 HTTP로 설정하고, http://localhost:3000/mcp를 가리키도록 한 뒤 Connect를 클릭하세요. 그다음 Tools → List Tools로 이동하여 fetch를 선택하고, URL을 입력한 뒤 실행하세요.
[!NOTE]
inspect스크립트는 포트 3000번을 하드코딩하고 있습니다. 만약 3000번 포트가 사용 중이라면, 서버는 임의의 포트로 전환되며 (실제 포트는 시작 로그를 확인하세요), 스크립트는 잘못된 곳을 가리키게 됩니다.package.json의 URL을 업데이트하거나, 인자 없이npx mcp-inspector를 실행한 후 Inspector 자체의 주소 필드에 올바른 URL을 입력하세요.
Inspector에는 CLI 모드도 있어, 스모크 테스트 (smoke test)를 수행하거나 스크립트로 작성하기에 더 빠르고 간편합니다:
npx mcp-inspector --cli http://localhost:3000/mcp --transport http --method tools/list
npx mcp-inspector --cli http://localhost:3000/mcp --transport http \
...
정상적인 tools/list는 완전히 구성된 JSON Schema (JSON Schema)와 함께 도구를 반환합니다. 만약 도구가 누락되었거나 스키마가 비어 있는 것처럼 보인다면, 위의 zod 섹션을 다시 확인하세요.
일반적인 클릭 스루(click-through) 워크스루를 보려면, v1 퀵스타트의 테스트 섹션을 참조하세요. 단계는 동일하지만, 해당 문서의 스크린샷은 Inspector v1을 보여줍니다. 현재 스캐폴드(scaffold)는 더 현대적인 UI를 갖추고 있으며 탐색이 눈에 띄게 더 직관적인 Inspector v2를 설치합니다.
기존 v1 서버 마이그레이션
이미 v1 서버를 보유하고 있다면, 마이그레이션은 기계적인 작업입니다. 다음 순서대로 진행하세요:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기