250줄의 TypeScript로 Claude를 위한 커스텀 도구 서버 구축하기
요약
Anthropic의 Model Context Protocol(MCP)을 사용하여 TypeScript로 커스텀 도구 서버를 구축하는 방법을 설명합니다. MCP를 통해 한 번 작성한 서버를 Claude Desktop, Cursor 등 다양한 클라이언트에서 재사용할 수 있는 개방형 표준의 이점을 다룹니다.
핵심 포인트
- MCP는 LLM에 실세계 도구를 제공하기 위한 개방형 표준 프로토콜임
- 한 번의 서버 구현으로 다양한 AI 클라이언트와 호환 가능
- JSON-RPC 2.0과 stdio를 기반으로 클라이언트-서버 간 통신 수행
- Anthropic의 공식 SDK를 사용하여 복잡한 프레이밍 처리 생략 가능
Claude Desktop을 여세요. 그리고 이렇게 물어보세요: "WebAssembly에 관한 Wikipedia 문서는 무엇을 담고 있나요?"
어떤 플러그인도 없다면, 모델은 학습 데이터(training data)를 바탕으로 추측할 것입니다. 하지만 제가 곧 보여드릴 서버를 사용하면, wiki_extract라는 이름의 도구(tool)를 호출하여 실제 문서를 실시간으로 가져와 읽어줄 것입니다. 동일한 클라이언트. 동일한 모델. 다른 도구들. 이 전환이 바로 Model Context Protocol (MCP)의 핵심입니다.
2024년 말 Anthropic에서 출시한 MCP는 _"LLM에게 어떻게 실세계의 도구를 제공할 것인가?"_라는 질문에 마침내 답을 제시하는 개방형 표준(open standard)입니다. MCP 이전에는 모든 AI 애플리케이션이 각자 자신만의 플러그인 시스템을 구축해야 했습니다. MCP 이후에는 서버를 한 번만 작성하면 Claude Desktop, Cursor, Continue.dev, Zed, 그리고 Anthropic SDK로 구축한 커스텀 에이전트(custom agents) 등 모든 호환 가능한 클라이언트가 동일한 방식으로 서버와 통신할 수 있습니다.
오늘 저는 여러분과 함께 서버를 구축하는 과정을 살펴보려 합니다. 서버 전체 코드는 단 250줄입니다. 정말 전부 다 말이죠.
멘탈 모델 (The mental model)
┌─────────────────┐ JSON-RPC 2.0 ┌──────────────────┐ HTTPS ┌──────────────┐
│ Claude Desktop │ ◄─── stdio ────► │ your MCP server │ ◄───────► │ Wikipedia │
│ (client) │ │ (Node.js) │ │ (REST API) │
...
이 삼각형 구조에서는 세 가지 일이 일어납니다:
- 클라이언트가 앱을 실행할 때 자식 프로세스(child process)로서 여러분의 서버를 생성합니다.
- 그들은 stdio를 통해 JSON-RPC 2.0으로 대화합니다 — 서버는 stdin으로부터 요청을 읽고 stdout으로 응답을 작성합니다. 이 전송 형식(wire format)은 Language Server Protocol (LSP)이 2016년부터 사용해 온 것과 동일합니다 (네, VS Code의 모든 "정의로 이동(Go to definition)" 기능 뒤에 있는 바로 그 프로토콜입니다).
- 모델이 도구를 호출할 시점을 결정합니다. 여러분의 서버는 도구를 제공하고, 모델은 어떤 도구를 어떤 인자(arguments)와 함께 호출할지 선택하며, 클라이언트는 호출을 라우팅(route)하고, 여러분의 서버는 이를 실행하여 결과를 반환합니다.
이것이 프로토콜의 전부입니다.
1단계: 스캐폴딩 (scaffold)
mcp-from-zero 디렉토리 생성 및 이동
mkdir mcp-from-zero && cd mcp-from-zero
npm init -y
npm install @modelcontextprotocol/sdk zod
...
@modelcontextprotocol/sdk는 Anthropic의 공식 SDK입니다. 이 SDK는 모든 JSON-RPC 프레이밍 (framing)을 대신 처리해 줍니다. 여러분은 도구 핸들러 (tool handlers)만 작성하면 되고, SDK가 와이어 포맷 (wire format)을 처리합니다.
2단계: stdio 전송 방식을 이용한 최소 기능 서버 구축
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
...
이것은 완전히 작동하는 MCP 서버입니다. 아직은 유용한 기능(도구)이 없지만, 클라이언트가 이 서버에 대해 initialize를 수행하고 기능(capabilities)을 읽을 수 있습니다. 단 9줄이면 충분합니다.
⚠️ 주의해야 할 함정: 절대 stdout (표준 출력)으로 로그를 남기지 마세요. stdio 전송 방식은 stdout에서 JSON-RPC 프레임을 읽어옵니다. 어떤 console.log라도 실행되면 와이어 포맷 (wire format)이 손상되어 클라이언트가 연결을 조용히 끊어버립니다. 모든 로그는 (stderr로 전달되는) console.error를 사용하세요.
3단계: 도구 추가하기
import { z } from 'zod'
server.tool(
...
여기서 주목해야 할 다섯 가지 사항이 있습니다:
server.tool(name, description, schema, handler)— 이것이 API의 전부입니다. SDK는tools/list및tools/callJSON-RPC 핸들러를 자동으로 연결합니다.- Zod 스키마 → JSON 스키마 (JSON Schema). SDK는 여러분의 Zod 정의를 JSON 스키마로 변환하여, MCP 클라이언트가 파라미터 문서화와 검증 (validation) 기능을 무료로 사용할 수 있게 합니다. 모델은 클라이언트가 보는 것과 동일한 스키마를 보게 되며, 어떤 인자 (arguments)를 보내야 하는지 알게 됩니다.
.describe()호출은 매우 중요합니다. 모델은 이를 자연어로 읽습니다. 설명이 더 좋을수록 도구 선택 성능이 향상됩니다. 단순히 "string"이라고 하는 것보다 "Search query, e.g. 'WebAssembly'"라고 하는 것이 훨씬 낫습니다.- 핸들러는 콘텐츠 블록 (content blocks)을 반환합니다. 각 블록은
type(text, image, resource_link)과 페이로드 (payload)를 가집니다. 모델은 이를 컨텍스트 (context)의 일부로 읽습니다. - 기본적으로 비동기 (Async)를 지원합니다. 도구는 데이터베이스에 접속하거나, API를 호출하거나, 서브프로세스 (subprocess)를 실행하는 등 무엇이든 할 수 있습니다. SDK는 결과를 기다리고 (await) 직렬화 (serialise)합니다.
4단계: Wikipedia 클라이언트
async function searchWikipedia(query: string, limit: number) {
const u = new URL('https://en.wikipedia.org/w/api.php')
u.searchParams.set('action', 'opensearch')
...
Wikipedia의 REST + Action API는 무료이며, 키나 계정이 필요하지 않습니다. 이들이 요구하는 것은 단 한 가지입니다. 정중한 User-Agent와 함께 연락 가능한 URL을 보내는 것입니다. 그래야만 정중한 트래픽을 차단하지 않고도 남용자를 제한(throttle)할 수 있기 때문입니다.
5단계: 리소스 (resources)
도구(Tools)는 **모델 (model)**이 호출하는 것입니다. 리소스(Resources)는 **클라이언트 (client)**가 스스로 가져오는 것입니다.
server.resource(
'wiki-trending',
'wiki://trending',
...
리소스는 클라이언트가 도구 호출에 모델 토큰 (model tokens)을 먼저 소비하지 않고도 주입하고 싶어 하는 컨텍스트(context)를 제공하는 데 매우 유용합니다. 예를 들어 일일 보고서, 용어 사전, 설정 파일, 최근 활동 등이 이에 해당합니다. 모델은 클라이언트가 대화에 포함시키기로 결정했을 때만 이를 볼 수 있습니다.
6단계: Claude Desktop에 연결하기
{
"mcpServers": {
"mcp-from-zero": {
...
이 내용을 Claude Desktop의 claude_desktop_config.json 파일에 넣으세요 (macOS: ~/Library/Application Support/Claude/, Windows: %APPDATA%/Claude/). 앱을 재시작합니다. 이제 오른쪽 하단의 도구 아이콘에 여러분의 세 가지 도구가 표시됩니다.
다음과 같이 시도해 보세요: "wiki_extract 도구를 사용하여 엔트로피(entropy)에 관한 기사를 요약해줘."
Claude가 도구를 선택하고, 여러분의 서버로 요청을 보내면, 서버가 Wikipedia에 접속하여 텍스트를 반환합니다. 그러면 Claude가 이를 읽고 요약을 작성합니다. 이 모든 루프가 채팅 내부에서 일어납니다.
MCP가 중요한 이유
지난 12개월 동안 AI 에이전트(AI-agents)의 흐름을 지켜보며 _"데모마다 도구 API가 다 다르네"_라고 생각했다면, MCP가 그 해답입니다.
- 하나의 서버가 모든 클라이언트에 적합합니다.
mcp-from-zero를 한 번만 구축하면 Claude Desktop, Cursor, Continue.dev, Zed, 그리고 Anthropic SDK를 가져다 쓰는 모든 커스텀 에이전트에서 작동합니다. - 벤더(vendor)를 가리지 않는 단일 규격입니다. Microsoft와 OpenAI 모두 MCP 지원을 공개적으로 약속했습니다. 주요 IDE 제작사들은 몇 달 만에 이를 출시했습니다.
- 사전 기능 협상 (Capability negotiation up front). 클라이언트는 모델 호출이 이루어지기 전에 서버에 자신이 무엇을 지원하는지(샘플링, 완성, 로깅 등)를 알려줍니다.
이것은 AI 통합을 위한 USB-C 모멘트입니다. 하나의 케이블로 모든 기기를 연결하는 것과 같습니다. MCP가 6개월 만에 "멋진 프로토콜"에서 "어디에나 존재하는 것"으로 변모한 이유는 **배포 모델 (distribution model)**이 마침내 적합해졌기 때문입니다. 한 번 구축하면 모든 채팅 / IDE / 에이전트에 동시에 배포할 수 있습니다.
이것이 여러분에게 바꾸어 놓을 것들
5년 전에는 Slack 봇을 작성했을 것입니다. 3년 전에는 ChatGPT 플러그인 (2024년 지원 중단)을 작성했을 것입니다. 작년에는 Custom GPT (OpenAI에 종속됨)를 작성했을 것입니다. 오늘날 여러분은 MCP 서버를 작성하여 npm에 푸시하기만 하면, 어떤 사용자의 기기에서든 실행되는 모든 AI 클라이언트가 이를 설치할 수 있습니다.
이 데모를 위한 코드는 GitHub에 있으며, 따라 할 수 있는 8개의 단계별 커밋이 포함되어 있습니다. 이를 클론(Clone)하고 2분 만에 Claude Desktop에 연결하면, 여러분이 직접 작성한 도구로 AI 클라이언트를 확장하게 됩니다. 이것이 바로 핵심입니다.
MCP에 오신 것을 환영합니다.
🔗 코드: github.com/dev48v/mcp-from-zero
📚 시리즈: TechFromZero — 매일 새로운 기술을 모두 무료로, 모두 오픈 소스로 제공합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기