MCP Server를 처음부터 구축하는 방법 (2026)
단계별로 Model Context Protocol 커스텀 서버 만들기
Hypereal로 구축 시작하기
단일 API를 통해 Kling, Flux, Sora, Veo 등에 액세스하세요. 무료 크레딧으로 시작하고 수백만으로 확장하세요.
신용카드 불필요 • 10만 명 이상의 개발자 • 엔터프라이즈 지원
MCP 서버를 처음부터 구축하는 방법 (2026)
Model Context Protocol (MCP)은 Anthropic에서 만든 오픈 표준으로, AI 어시스턴트가 통합된 인터페이스를 통해 외부 도구 및 데이터 소스에 연결할 수 있게 해줍니다. 모든 AI 클라이언트마다 맞춤형 통합 기능을 빌드하는 대신, 하나의 MCP 서버만 구축하면 Claude Desktop, Cursor, VS Code 및 기타 모든 MCP 호환 클라이언트에서 작동합니다.
이 가이드는 TypeScript를 사용하여 처음부터 완전한 MCP 서버를 구축하는 과정을 안내합니다. 마지막 단계에 이르면 커스텀 도구(tools), 리소스(resources), 프롬프트(prompts)를 모든 MCP 클라이언트에 노출하는 작동하는 서버를 갖게 됩니다.
구축할 내용
가상의 프로젝트 관리 시스템을 위한 MCP 서버를 구축할 것입니다. 기능은 다음과 같습니다:
- AI가 호출할 수 있는 도구 노출 (작업 생성, 작업 목록 조회, 상태 업데이트)
- 컨텍스트를 제공하는 리소스 노출 (프로젝트 데이터, 팀 정보)
- 재사용 가능한 상호작용 패턴을 정의하는 프롬프트 노출
사전 요구 사항
| 요구 사항 | 상세 내용 |
|---|---|
| Node.js | v18 이상 |
| TypeScript | v5+ |
| npm 또는 yarn | 패키지 관리용 |
| 기본 TypeScript 지식 | 함수, 타입, async/await |
| MCP 클라이언트 | Claude Desktop, Cursor 또는 VS Code |
1단계: 프로젝트 초기화
새 프로젝트를 생성하고 MCP SDK를 설치합니다:
mkdir my-mcp-server
cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
TypeScript 설정:
npx tsc --init
tsconfig.json 업데이트:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"declaration": true
},
"include": ["src/**/*"]
}
빌드 스크립트를 추가하고 타입을 설정하기 위해 package.json을 업데이트합니다:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node dist/index.js",
"dev": "tsx src/index.ts"
},
"bin": {
"my-mcp-server": "./dist/index.js"
}
}
2단계: 기본 서버 생성
MCP 서버 스캐폴딩이 포함된 src/index.ts를 생성합니다:
#!/usr/bin/env node
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
// 인메모리 작업 저장소 (프로덕션에서는 데이터베이스로 교체하세요)
interface Task {
id: string;
title: string;
description: string;
status: "todo" | "in_progress" | "done";
assignee: string;
createdAt: string;
}
const tasks: Map<string, Task> = new Map();
let nextId = 1;
// MCP 서버 생성
const server = new McpServer({
name: "project-manager",
version: "1.0.0",
});
console.error("MCP server initialized"); // 로그용 stderr (stdout은 MCP 프로토콜용으로 예약됨)
이해해야 할 핵심 사항: MCP 서버는 기본적으로 stdio를 통해 통신합니다. 모든 프로토콜 메시지는 stdout을 통해 전달되므로, 모든 로깅은 반드시 stderr로 보내야 합니다.
3단계: 도구(Tools) 추가
도구는 AI 어시스턴트가 호출할 수 있는 함수입니다. 이들은 대부분의 MCP 서버의 핵심입니다. src/index.ts에 다음 코드를 추가하세요:
// 도구: 새 작업 생성
server.tool(
"create_task",
"Create a new task in the project",
{
title: z.string().describe("The task title"),
description: z.string().describe("Detailed description of the task"),
assignee: z.string().describe("Name of the person assigned to the task"),
},
async ({ title, description, assignee }) => {
const id = `TASK-${nextId++}`;
const task: Task = {
id,
title,
description,
status: "todo",
assignee,
createdAt: new Date().toISOString(),
};
tasks.set(id, task);
return {
content: [
{
type: "text",
text: `Created task ${id}: "${title}" assigned to ${assignee}`,
},
],
};
}
);
// 도구: 전체 작업 목록 조회
server.tool(
"list_tasks",
"List all tasks, optionally filtered by status or assignee",
{
status: z
.enum(["todo", "in_progress", "done"])
.optional()
.describe("Filter by status"),
assignee: z.string().optional().describe("Filter by assignee name"),
},
async ({ status, assignee }) => {
let filtered = Array.from(tasks.values());
if (status) {
filtered = filtered.filter((t) => t.status === status);
}
if (assignee) {
filtered = filtered.filter((t) =>
t.assignee.toLowerCase().includes(assignee.toLowerCase())
);
}
if (filtered.length === 0) {
return {
content: [{ type: "text", text: "No tasks found matching the criteria." }],
};
}
const taskList = filtered
.map(
(t) =>
`- [${t.id}] ${t.title} (${t.status}) - Assigned to: ${t.assignee}`
)
.join("\n");
return {
content: [{ type: "text", text: taskList }],
};
}
);
// 도구: 작업 상태 업데이트
server.tool(
"update_task_status",
"Update the status of an existing task",
{
taskId: z.string().describe("The task ID (e.g., TASK-1)"),
status: z
.enum(["todo", "in_progress", "done"])
.describe("The new status"),
},
async ({ taskId, status }) => {
const task = tasks.get(taskId);
if (!task) {
return {
content: [{ type: "text", text: `Task ${taskId} not found.` }],
isError: true,
};
}
const oldStatus = task.status;
task.status = status;
return {
content: [
{
type: "text",
text: `Updated ${taskId}: ${oldStatus} → ${status}`,
},
],
};
}
);
도구의 구조
각 도구 등록은 네 가지 부분으로 구성됩니다:
- Name -- AI가 도구를 호출하는 데 사용하는 고유 식별자
- Description -- 이 도구를 언제, 왜 사용해야 하는지 AI에게 알려줌
- Schema -- 매개변수를 정의하는 Zod 스키마 (자동으로 검증됨)
- Handler -- 도구를 실행하고 결과를 반환하는 비동기 함수
4단계: 리소스(Resources) 추가
리소스는 AI에게 읽기 전용 컨텍스트를 제공합니다. 도구와 달리 행동을 수행하지 않고 데이터를 제공합니다.
// 리소스: 프로젝트 요약
server.resource(
"project-summary",
"project://summary",
async (uri) => {
const totalTasks = tasks.size;
const byStatus = {
todo: Array.from(tasks.values()).filter((t) => t.status === "todo").length,
in_progress: Array.from(tasks.values()).filter(
(t) => t.status === "in_progress"
).length,
done: Array.from(tasks.values()).filter((t) => t.status === "done").length,
};
const summary = `# Project Summary
Total tasks: ${totalTasks}
- To Do: ${byStatus.todo}
- In Progress: ${byStatus.in_progress}
- Done: ${byStatus.done}
Completion rate: ${totalTasks > 0 ? Math.round((byStatus.done / totalTasks) * 100) : 0}%`;
return {
contents: [
{
uri: uri.href,
mimeType: "text/markdown",
text: summary,
},
],
};
}
);
5단계: 프롬프트(Prompts) 추가
프롬프트는 AI 클라이언트가 사용자에게 제공할 수 있는 재사용 가능한 템플릿입니다. 구조화된 상호작용 패턴을 정의합니다:
// 프롬프트: 스프린트 계획
server.prompt(
"sprint-planning",
"Generate a sprint planning summary based on current tasks",
{
sprintName: z.string().describe("Name of the sprint (e.g., Sprint 23)"),
},
async ({ sprintName }) => {
const todoTasks = Array.from(tasks.values()).filter(
(t) => t.status === "todo"
);
const taskListText =
todoTasks.length > 0
? todoTasks.map((t) => `- ${t.id}: ${t.title} (${t.assignee})`).join("\n")
: "No pending tasks.";
return {
messages: [
{
role: "user",
content: {
type: "text",
text: `Create a sprint planning document for "${sprintName}". Here are the current unassigned/todo tasks:\n\n${taskListText}\n\nPlease organize them by priority, estimate story points, and suggest a sprint goal.`,
},
},
],
};
}
);
6단계: 서버 시작
src/index.ts 하단에 서버 시작 코드를 추가합니다:
// 서버 시작
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("Project Manager MCP server running on stdio");
}
main().catch((error) => {
console.error("Fatal error:", error);
process.exit(1);
});
빌드 및 테스트:
# 빌드
npm run build
# 직접 실행하여 테스트 (stdin에서 MCP 메시지를 기다리는 서버 확인 가능)
echo '{"jsonrpc":"2.0","method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"test","version":"1.0"}},"id":1}' | node dist/index.js
7단계: Claude Desktop에 연결
Claude Desktop의 설정 파일에 서버를 추가합니다.
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
}
}
}
Claude Desktop을 다시 시작합니다. 세 개의 사용 가능한 도구가 포함된 project-manager 서버가 표시되는 도구 아이콘이 보여야 합니다.
8단계: Cursor에 연결
Cursor의 경우, 워크스페이스 루트에 있는 .cursor/mcp.json에 MCP 서버를 추가합니다:
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
}
}
}
Cursor를 다시 시작하면 AI 채팅에서 해당 도구들을 사용할 수 있게 됩니다.
서버 테스트
MCP SDK에는 디버깅을 위한 인스펙터 도구가 포함되어 있습니다:
npx @modelcontextprotocol/inspector node dist/index.js
그러면 다음과 같은 작업을 수행할 수 있는 웹 UI가 열립니다:
- 등록된 모든 도구, 리소스 및 프롬프트 확인
- 테스트 입력으로 도구 호출
- 원시 MCP 프로토콜 메시지 보기
- 실시간 에러 디버깅
프로젝트 구조
최종 프로젝트 구조는 다음과 같아야 합니다:
my-mcp-server/
src/
index.ts # 메인 서버 파일
dist/ # 컴파일된 결과물
package.json
tsconfig.json
규모가 큰 서버의 경우 별도의 파일로 분리하세요:
my-mcp-server/
src/
index.ts # 서버 설정 및 시작
tools/
tasks.ts # 작업 관련 도구
reports.ts # 보고서 생성 도구
resources/
project.ts # 프로젝트 리소스
prompts/
planning.ts # 계획 프롬프트 템플릿
types.ts # 공통 타입
dist/
package.json
tsconfig.json
공통 패턴
데이터베이스 연결
인메모리 Map을 데이터베이스 클라이언트로 교체합니다:
import Database from "better-sqlite3";
const db = new Database("./tasks.db");
db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
description TEXT,
status TEXT DEFAULT 'todo',
assignee TEXT,
created_at TEXT DEFAULT (datetime('now'))
)
`);
인증 추가
MCP 서버가 외부 API에 접속해야 하는 경우 환경 변수를 통해 인증 정보를 전달합니다:
{
"mcpServers": {
"project-manager": {
"command": "node",
"args": ["/path/to/dist/index.js"],
"env": {
"API_KEY": "your-secret-key",
"DATABASE_URL": "postgresql://localhost/mydb"
}
}
}
}
서버에서 해당 정보에 접근합니다:
const apiKey = process.env.API_KEY;
if (!apiKey) {
console.error("API_KEY environment variable is required");
process.exit(1);
}
마무리하며
도구(actions), 리소스(data), 프롬프트(templates)라는 세 가지 기본 개념만 이해하면 MCP 서버 구축은 매우 간단합니다. MCP SDK가 모든 프로토콜 세부 사항을 처리하므로, 개발자는 애플리케이션에 중요한 로직에만 집중할 수 있습니다.
MCP는 AI 어시스턴트가 외부 세계와 상호작용하는 표준 방식으로 빠르게 자리 잡고 있습니다. 직접 서버를 구축하면 AI가 무엇에 접근하고 무엇을 할 수 있는지 완벽하게 제어할 수 있습니다.
AI 프로젝트에 이미지, 비디오 또는 말하는 아바타와 같은 미디어 생성이 포함된다면, 모든 주요 AI 미디어 모델을 아우르는 통합 API인 Hypereal AI를 확인해 보세요.
Hypereal AI 무료 체험하기 -- 신용카드 없이 35 크레딧 제공.
