2026. 10. 6. 12:10ㆍMCP & 도구 연동
지난 글에서는 Anthropic이 공개한 Model Context Protocol(MCP)의 기본 개념과 Claude Code에 기성 MCP 도구들을 연동하는 방법을 살펴보았습니다.
Brave 검색, GitHub, SQLite 등 오픈소스로 이미 잘 만들어진 MCP 서버들도 훌륭하지만, 실무에서 AI 에이전트를 진정으로 유용하게 쓰려면 결국 '내 로컬 환경, 사내 비공개 API, 나만의 자동화 스크립트'를 에이전트의 손발로 만들어 주어야 합니다.
이번 글에서는 공식 @modelcontextprotocol/sdk를 활용해 Node.js(TypeScript) 환경에서 30분 만에 동작하는 커스텀 MCP 서버를 직접 구현하고, Claude Code에 연결해 자연어로 제어하는 실전 튜토리얼을 진행합니다.
1. MCP 통신 구조 한눈에 이해하기
커스텀 서버 코드를 작성하기 전에, Claude Code와 MCP 서버가 어떻게 대화를 나누는지 전체적인 구조를 시각적으로 살펴보겠습니다.

▲ Model Context Protocol (MCP) 표준 통신 아키텍처 다이어그램
위 다이어그램처럼 MCP의 통신 흐름은 매우 직관적이고 표준화되어 있습니다:
- 클라이언트(Claude Code): 사용자의 자연어 요청을 해석한 뒤, 작업 수행에 적합한 도구를 골라 JSON-RPC 규격의 요청 메시지를 보냅니다.
- 트랜스포트 계층(Stdio Transport): 별도의 HTTP 포트를 열지 않고 표준 입력(stdin)과 표준 출력(stdout) 파이프를 통해 빠르고 안전하게 데이터를 주고받습니다.
- 커스텀 MCP 서버: 우리가 직접 작성한 비즈니스 로직(도구 실행, 로컬 시스템 진단, DB 쿼리 등)을 처리한 뒤, 결과를 다시 표준 규격의 JSON 응답으로 반환합니다.
2. 프로젝트 초기화 및 환경 세팅
TypeScript 기반의 초경량 MCP 서버 프로젝트를 생성합니다. 터미널을 열고 다음 명령어를 순서대로 실행해 주세요.
mkdir my-custom-mcp
cd my-custom-mcp
npm init -y
(1) 필수 의존성 패키지 설치
# MCP 공식 SDK 및 입력 검증용 Zod 설치
npm install @modelcontextprotocol/sdk zod
# 개발 및 빌드용 TypeScript 도구 설치
npm install -D typescript @types/node tsx
(2) package.json 설정 변경
ES 모듈(ESM)을 사용할 수 있도록 package.json에 "type": "module"과 실행 스크립트를 추가합니다:
{
"name": "my-custom-mcp",
"version": "1.0.0",
"type": "module",
"bin": {
"my-custom-mcp": "./dist/index.js"
},
"scripts": {
"build": "tsc",
"start": "node ./dist/index.js",
"dev": "tsx src/index.ts"
}
}
(3) tsconfig.json 생성
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
3. 실전 예제: 시스템 진단 & 디스크 검사 MCP 서버 작성
이제 본격적인 서버 코드를 작성해 보겠습니다. 에이전트가 로컬 머신의 메모리 상태, CPU 부하, 디스크 용량을 실시간으로 체크할 수 있는 진단 도구를 만들어 보겠습니다.
src/index.ts 파일을 생성하고 아래 코드를 작성합니다:
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
import os from "node:os";
// 1. MCP 서버 인스턴스 초기화
const server = new Server(
{
name: "system-diagnostic-mcp",
version: "1.0.0",
},
{
capabilities: {
tools: {}, // 도구 기능 활성화
},
}
);
// 2. 도구 목록(Tools Catalog) 정의
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "get_system_metrics",
description: "현재 컴퓨터의 CPU 아키텍처, 사용 가능한 메모리, 시스템 가동 시간(Uptime) 정보를 반환합니다.",
inputSchema: {
type: "object",
properties: {
detailed: {
type: "boolean",
description: "true일 경우 개별 CPU 코어별 상세 정보를 포함합니다.",
},
},
},
},
],
};
});
// 3. 도구 실행 핸들러(Tool Execution) 구현
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === "get_system_metrics") {
// Zod를 통한 입력 파라미터 안전 검증
const schema = z.object({
detailed: z.boolean().optional().default(false),
});
const parsed = schema.safeParse(args ?? {});
if (!parsed.success) {
throw new Error("유효하지 않은 인자 형식입니다: " + parsed.error.message);
}
const freeMemMb = Math.round(os.freemem() / (1024 * 1024));
const totalMemMb = Math.round(os.totalmem() / (1024 * 1024));
const memUsagePercent = Math.round(((totalMemMb - freeMemMb) / totalMemMb) * 100);
const metricsData = {
platform: os.platform(),
arch: os.arch(),
uptimeMinutes: Math.round(os.uptime() / 60),
memory: {
total: totalMemMb + " MB",
free: freeMemMb + " MB",
usage: memUsagePercent + "%",
},
loadAverage: os.loadavg(),
cpus: parsed.data.detailed ? os.cpus() : { count: os.cpus().length },
};
return {
content: [
{
type: "text",
text: JSON.stringify(metricsData, null, 2),
},
],
};
}
throw new Error("지원하지 않는 도구 이름입니다: " + name);
});
// 4. Stdio 트랜스포트로 서버 실행
async function run() {
const transport = new StdioServerTransport();
await server.connect(transport);
// 주의: stdio 통신 중이므로 console.log는 표준 출력을 깨뜨릴 수 있습니다.
console.error("시스템 진단 MCP 서버가 stdio 모드로 안전하게 시작되었습니다.");
}
run().catch((error) => {
console.error("MCP 서버 구동 중 치명적 오류 발생:", error);
process.exit(1);
});
4. 로컬 빌드 및 Claude Code에 등록하기
(1) 빌드 실행
npm run build
정상적으로 컴파일되면 dist/index.js 파일이 생성됩니다.
(2) Claude Code에 글로벌 도구로 등록
터미널에서 claude mcp add 명령어를 사용해 방금 만든 서버를 글로벌 유저 스코프에 등록합니다:
claude mcp add -s user system-diagnostic -- node /절대경로/my-custom-mcp/dist/index.js
(3) 정상 연결 확인
claude mcp list
출력 결과에서 system-diagnostic: node ... (Connected)가 보인다면 등록이 완벽하게 끝난 것입니다.
5. Claude Code에서 자연어로 테스트해보기
이제 Claude Code 터미널을 열고 평소처럼 자연어로 지시해 봅니다:
claude
# Claude Code 프롬프트 안에서 입력:
> "지금 내 컴퓨터 시스템 자원 상태가 어떤지 점검해줘"
에이전트의 내부 실행 과정:
- Claude가 질문을 분석하고
system-diagnostic-mcp에 등록된get_system_metrics도구가 적합함을 자동 판단합니다. get_system_metrics({ detailed: false })호출을 백그라운드로 전송합니다.- 우리가 작성한 Node.js 코드가 메모리와 부하율을 계산해 JSON으로 돌려줍니다.
- Claude가 응답 결과를 바탕으로 아래와 같이 친절하게 답변을 구성합니다:
"현재 시스템 상태를 진단했습니다. 플랫폼은 linux(x64)이며, 전체 32GB 메모리 중 18GB(56%)를 사용 중입니다. 시스템 가동 시간은 약 1,240분이며 부하율은 0.85로 매우 안정적인 상태입니다."
6. 커스텀 MCP 개발 시 놓치기 쉬운 2가지 핵심 함정
함정 1: Stdio 모드에서 console.log 사용 금지!
Stdio 트랜스포트는 표준 출력(stdout) 전체가 JSON-RPC 규격의 메시지 파이프라인으로 사용됩니다. 만약 디버깅을 위해 코드에 console.log("변수값: ", data)를 남겨두면, JSON-RPC 메시지 중간에 평문 텍스트가 섞이면서 클라이언트의 JSON 파서가 깨져버립니다.
- 디버그용 로그는 반드시
console.error()를 사용해야 표준 에러(stderr)로 분리되어 안전하게 전달됩니다.
함정 2: 입출력 스키마는 최대한 구체적으로 설명(Description) 작성하기
에이전트가 어떤 상황에서 이 도구를 꺼내 쓸지는 description 문구에 전적으로 의존합니다. 설명이 부실하면 에이전트가 엉뚱한 순간에 도구를 호출하거나 필요한 순간에 도구를 지나칠 수 있으므로, "언제, 어떤 파라미터로, 어떤 결과가 나오는지"를 한국어나 영어로 명확히 적어주는 것이 좋습니다.
마치며: 나만의 전용 에이전트 구축하기
단 몇 줄의 TypeScript 코드만으로 우리는 로컬 머신의 내부 기능을 AI 에이전트의 공식 도구로 확장할 수 있었습니다.
이 방식을 응용하면:
- 사내 결재 시스템이나 지라(Jira) 티켓을 자동으로 긁어오는 MCP
- 도커(Docker) 컨테이너 상태를 검사하고 장애 시 재시작하는 DevOps MCP
- 블로그 포스팅이나 소셜 미디어 배포를 한 번에 끝내는 마케팅 자동화 MCP
등 상상하는 모든 워크플로우를 AI 에이전트의 손과 발로 연결할 수 있습니다.
다음 글에서는 "텔레그램과 슬랙 봇을 AI 에이전트와 연동하여, 스마트폰 채팅으로 서버를 관리하고 자동 배포하는 풀스택 자동화 워크플로우"를 다뤄보겠습니다.
'MCP & 도구 연동' 카테고리의 다른 글
| Claude Code에 날개 달기: Model Context Protocol(MCP) 완벽 가이드와 실무 추천 도구 연동 (0) | 2026.10.03 |
|---|---|
| 해킹으로 인한 가상화폐 피해 금액 50%, 북한 소행으로 추정 (0) | 2024.08.14 |
| 라온시큐어 키보드 보안 솔루션에서 취약점 발견... 빠른 업데이트 필요 (0) | 2024.08.14 |