첫 MCP 서버 만들기 — TypeScript로 30분 완성
TypeScript로 실제 동작하는 MCP 서버를 처음부터 만들어 봅니다. 프로젝트 생성, 도구 등록, Claude Code 연동까지 30분 안에 완성하는 실전 가이드.

준비물
MCP 서버를 만들기 위해 필요한 것은 딱 세 가지입니다. 개발 환경이 이미 갖춰진 개발자라면 추가 설치 없이 바로 시작할 수 있고, 처음 시작하는 경우에도 30분 이내에 실제로 동작하는 서버를 손에 넣을 수 있습니다. 특히 중요한 점은 MCP가 TypeScript와 Python을 공식 SDK로 제공한다는 점이며, 이 글에서는 생태계 주류인 TypeScript 경로를 따릅니다.
- Node.js 18+ (LTS 권장) — MCP SDK는 18 이상에서 동작하며, Node 20 LTS가 현시점 권장 런타임입니다
- npm, pnpm, yarn 또는 bun 중 하나 — 어느 매니저를 쓰든 동일하게 동작합니다
- Claude Code 또는 다른 MCP 호환 클라이언트 (Cursor, Windsurf, Zed 등)
!mcp-guide-02-first-server 본문 이미지 1
1단계: 프로젝트 생성
Anthropic이 제공하는 공식 CLI로 프로젝트를 스캐폴딩합니다. 이 CLI는 MCP SDK 의존성, tsconfig, 진입점 src/index.ts, 빌드 스크립트까지 한 번에 세팅해 주므로, 수동으로 package.json을 작성하는 수고를 줄여 줍니다. 내부적으로는 TypeScript 5.x와 @modelcontextprotocol/sdk, zod 등 필수 패키지가 포함됩니다.
# 공식 create-server CLI로 프로젝트 생성npx @modelcontextprotocol/create-server my-first-mcp
생성된 프로젝트로 이동
cd my-first-mcp
의존성 설치
npm install
생성된 디렉토리 구조는 다음과 같습니다.
my-first-mcp/├── src/
│ └── index.ts # 메인 서버 코드
├── package.json
├── tsconfig.json
└── README.md
2단계: 첫 번째 도구 등록
src/index.ts를 열어 실제 동작하는 도구를 등록합니다. 여기서는 현재 시간 반환과 텍스트 변환 두 가지 도구를 만들어 봅니다. 의도적으로 외부 API를 쓰지 않는 순수 함수 두 개를 골랐습니다. 네트워크 오류·인증 이슈 없이 "서버가 정상 기동되고 Claude Code가 도구를 인식했는지"만 빠르게 검증할 수 있기 때문입니다. 첫 서버는 동작 확인이 우선이고, 외부 API 연동은 이후 편에서 안전하게 확장하는 편이 실패 비용이 낮습니다.
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 서버 인스턴스 생성
const server = new McpServer({
name: "my-first-mcp",
version: "1.0.0",
});
// 도구 1: 현재 시간 반환
server.tool(
"get_current_time",
"현재 시간을 지정된 타임존으로 반환합니다",
{ timezone: z.string().default("Asia/Seoul").describe("IANA 타임존 문자열") },
async ({ timezone }) => {
const now = new Date().toLocaleString("ko-KR", { timeZone: timezone });
return {
content: [{ type: "text", text:
현재 시간 (${timezone}): ${now}}],};
}
);
// 도구 2: 텍스트 변환
server.tool(
"transform_text",
"텍스트를 지정된 형식으로 변환합니다",
{
text: z.string().describe("변환할 텍스트"),
format: z.enum(["uppercase", "lowercase", "reverse", "slug"]).describe("변환 형식"),
},
async ({ text, format }) => {
const transformers: Record<string, (s: string) => string> = {
uppercase: (s) => s.toUpperCase(),
lowercase: (s) => s.toLowerCase(),
reverse: (s) => [...s].reverse().join(""),
slug: (s) => s.toLowerCase().replace(/\s+/g, "-").replace(/[^a-z0-9가-힣-]/g, ""),
};
const result = transformersformat;
return { content: [{ type: "text", text: result }] };
}
);
// 서버 시작
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("My First MCP 서버가 시작되었습니다");
}
main().catch(console.error);
핵심 패턴은 간단합니다. server.tool(이름, 설명, Zod스키마, 핸들러) 네 가지만 기억하면 됩니다. 이름은 스네이크 케이스를 권장하고, 설명은 "언제 이 도구를 써야 하는가"를 한 문장으로 적습니다. Claude 같은 모델은 이 설명만 읽고 도구 선택 여부를 결정하기 때문에, 설명이 모호하면 호출 누락이나 잘못된 호출이 늘어납니다. Zod 스키마는 런타임 유효성 검사와 타입 추론을 동시에 해 주므로, 모델이 잘못된 파라미터를 넘겨도 서버 코드가 안전합니다.
!mcp-guide-02-first-server 본문 이미지 2
3단계: 빌드 및 테스트
작성한 TypeScript 코드를 실제 Node.js가 실행할 수 있는 JavaScript로 컴파일합니다. MCP는 기본적으로 표준 입출력(stdio) 위에서 JSON-RPC 2.0 메시지를 주고받기 때문에, 별도의 HTTP 서버를 띄울 필요 없이 node dist/index.js만 실행하면 됩니다.
# TypeScript 빌드npm run build
직접 실행하여 동작 확인 (stdio 기반이므로 JSON-RPC로 통신)
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | node dist/index.js
빌드 후 dist/index.js가 생성됩니다. 이 파일이 MCP 서버의 진입점입니다. 위 명령어는 tools/list라는 MCP 표준 메서드를 서버에 직접 호출해 등록된 도구 목록을 응답으로 확인하는 방식으로, 클라이언트 없이도 서버가 정상적으로 동작하는지 빠르게 검증할 수 있습니다.
4단계: Claude Code에 연결
Claude Code의 MCP 설정 파일에 서버를 등록합니다. 설정은 전역 혹은 프로젝트 단위로 관리할 수 있으며, 팀 전체가 동일한 서버를 쓰려면 프로젝트 로컬 설정을 Git에 포함시키는 것이 유지보수에 유리합니다. 토큰이 들어가는 서버라면 설정에는 환경 변수 참조만 두고, 실제 값은 .env 혹은 OS 시크릿 저장소로 분리해야 합니다.
{"mcpServers": {
"my-first-mcp": {
"command": "node",
"args": ["/absolute/path/to/my-first-mcp/dist/index.js"]
}
}
}
설정 파일 위치는 다음과 같습니다.
| 범위 | 경로 |
| 사용자 전역 | ~/.claude.json |
| 프로젝트 | .claude/settings.local.json |
get_current_time과 transform_text 도구가 자동으로 인식됩니다.
# Claude Code에서 바로 사용claude "지금 서울 시간이 몇 시야?"
→ get_current_time 도구가 자동 호출됨
claude "hello world를 slug 형식으로 변환해줘"
→ transform_text 도구가 자동 호출됨
5단계: 리소스 추가 (보너스)
도구 외에 리소스도 추가해 봅시다. 리소스는 모델이 참조할 수 있는 읽기 전용 데이터입니다. 도구가 "실행 가능한 액션"이라면 리소스는 "맥락으로 읽어들이는 문서"에 가깝습니다. 예를 들어 프로젝트 README, DB 스키마, 서버 상태 같은 정보는 모델이 매 호출마다 액션으로 가져오는 것보다 리소스로 노출해 두는 편이 토큰 경제성에서 유리합니다.
server.resource("server://status",
"서버 상태 정보",
async () => ({
contents: [{
uri: "server://status",
text: JSON.stringify({
uptime: process.uptime(),
memory: process.memoryUsage().heapUsed,
version: "1.0.0",
tools: ["get_current_time", "transform_text"],
}, null, 2),
}],
})
);
트러블슈팅
첫 MCP 서버를 붙일 때 가장 흔한 문제 세 가지를 원인·해결법과 함께 정리합니다. 대부분의 이슈는 빌드 누락이나 절대 경로 오타에서 비롯되므로, 설정을 의심하기 전에 먼저 dist/index.js를 직접 실행해 크래시 여부를 확인하는 습관이 가장 빠른 디버깅 방법입니다.
| 증상 | 원인 | 해결 |
| 도구가 목록에 안 보임 | 빌드 안 됨 | npm run build 재실행 |
| 연결 실패 | 경로 오류 | args의 절대 경로 확인 |
| 타임아웃 | 서버 크래시 | node dist/index.js로 직접 실행하여 에러 확인 |
다음 단계
직접 만든 MCP 서버가 동작하는 것을 확인했다면, 다음 편에서는 Filesystem MCP를 활용한 파일 시스템 자동화를 다룹니다. 이미 만들어진 공식 서버를 활용하는 방법을 배워 봅시다.
MCP 실전 가이드 시리즈
AI K LINK Kit에서 검증된 MCP 서버를 탐색하세요. 문서에서 상세 설정 가이드를 확인할 수 있습니다.
- MCP란 무엇인가 — Model Context Protocol 완전 이해
- 2첫 MCP 서버 만들기 — TypeScript로 30분 완성읽는 중
- 3Filesystem MCP — 파일 시스템 자동화 실전
관련 글
이 글은 AI K LINK 콘텐츠팀이 작성하였으며, AI 도구의 도움을 받아 리서치 및 초안 작성이 이루어졌습니다. 최종 발행 전 전문 에디터의 검수를 거칩니다. 내용에 대한 문의는 contact@aiklink.com으로 보내주세요.


