첫 번째 MCP 서버 만들기

첫 번째 MCP 서버 만들기 (Build your first server)

MCP 서버는 모델이 호출할 수 있는 도구를 드러내는 프로그램이에요. 이 튜토리얼에서는 미국 기상 특보를 조회하는 도구 하나를 가진 서버를 만들고, 그것을 클라이언트에서 직접 호출해 볼 거예요.

프로젝트 준비하기

필요한 건 Node.js 20 이상뿐이에요. 프로젝트를 만들고 SDK를 설치하면 됩니다.

mkdir weather && cd weather
npm init -y
npm pkg set type=module
npm install @modelcontextprotocol/server zod tsx
mkdir src

type=module 이 중요한데, SDK가 ES 모듈만 지원하기 때문이에요. tsx 는 TypeScript를 바로 실행해 주므로 빌드 단계가 필요 없습니다.

도구 등록하기

src/index.ts 를 만듭니다. 여기에는 createServer 팩토리가 있어서 McpServer 를 만들고 도구 하나 — 즉 연결된 모델이 호출할 수 있는 함수 — 를 등록해요.

import { McpServer } from '@modelcontextprotocol/server';
import { serveStdio } from '@modelcontextprotocol/server/stdio';
import * as z from 'zod/v4';

const NWS_API = 'https://api.weather.gov';

interface AlertsResponse {
    features: { properties: { event?: string; headline?: string } }[];
}

function createServer(): McpServer {
    const server = new McpServer({ name: 'weather', version: '1.0.0' });

    server.registerTool(
        'get-alerts',
        {
            description: 'Get the active weather alerts for a US state',
            inputSchema: z.object({
                state: z.string().length(2).describe('Two-letter US state code, e.g. CA')
            })
        },
        async ({ state }) => {
            const code = state.toUpperCase();
            const url = `${NWS_API}/alerts/active?area=${code}`;
            const res = await fetch(url, { headers: { 'User-Agent': 'mcp-weather-tutorial/1.0' } });
            if (!res.ok) {
                return { content: [{ type: 'text', text: `NWS API error: HTTP ${res.status}` }], isError: true };
            }
            const { features } = (await res.json()) as AlertsResponse;
            if (features.length === 0) {
                return { content: [{ type: 'text', text: `No active alerts for ${code}.` }] };
            }
            const lines = features.map(f => f.properties.headline ?? f.properties.event ?? 'Unnamed alert');
            return { content: [{ type: 'text', text: lines.join('\n') }] };
        }
    );

    return server;
}

registerTool 은 이름, 설정, 비동기 핸들러 세 가지를 받아요. inputSchema 는 Zod 스키마인데, 여러분이 직접 쓰는 유일한 스키마예요. SDK는 이 하나의 스키마에서 모델이 보게 될 JSON Schema를 만들어 내고, 핸들러가 실행되기 전에 인자를 검증하며, 핸들러의 인자 타입까지 추론합니다.

핸들러는 content — 타입이 정해진 블록들의 목록 — 를 반환해요. 여기서는 text 블록 하나를 돌려주죠. isError: true 는 모델이 읽고 반응할 수 있는 실패 결과를 표시합니다.

::: tip get-alerts{ "state": "California" } 로 호출하면 SDK가 핸들러 실행 전에 거부해요. 그 결과가 바로 모델이 보는 실패 메시지입니다.

Input validation error: Invalid arguments for tool get-alerts: state: Too big: expected string to have <=2 characters

:::

stdio 위에서 서빙하기

파일 끝에서 createServer 팩토리를 serveStdio 에 넘겨줍니다.

void serveStdio(createServer);
console.error('weather MCP server running on stdio');

serveStdiostdio 전송을 소유해요. stdin에서 요청을 읽고 stdout으로 응답을 쓰며, 연결을 서빙할 인스턴스를 만들기 위해 createServer 를 호출합니다.

::: warning stdout은 프로토콜 채널이에요. 로그는 console.error 로 남기세요 — console.log 하나가 JSON-RPC 스트림을 망가뜨립니다. :::

실행해 보기

프로젝트 루트에서 서버를 시작합니다.

npx tsx src/index.ts

배너는 stderr로 출력되어 stdout은 프로토콜용으로 남습니다.

weather MCP server running on stdio

다른 일은 벌어지지 않아요. stdio 서버는 클라이언트가 대화를 시작할 때까지 stdin에서 기다리기만 하니까요. Ctrl+C 로 멈추면 됩니다.

도구 호출하기

MCP Inspector 는 서버의 도구를 직접 호출해 보는 로컬 웹 앱이에요. 여러분이 준 명령을 실행해 stdio로 연결해 줍니다.

npx @modelcontextprotocol/inspector npx tsx src/index.ts

열린 브라우저 탭에서 Connect 를 누르고, Tools 탭에서 get-alerts 를 선택한 뒤 TX 같은 두 글자 주 코드를 넣고 실행하면 됩니다. 결과의 text 블록에 그 주의 활성 특보 헤드라인이 한 줄씩 나열돼요 — 모델이 여러분의 도구를 호출할 때 받는 것과 똑같은 내용이에요.

전송 고르기

여러분의 서버는 호스트가 로컬 프로세스로 띄우고 생명주기를 소유하기 때문에 stdio로 말하고 있어요. 여러 클라이언트가 붙는 하나의 엔드포인트를 호스팅하려면, 같은 createServer 팩토리를 HTTP로 서빙하면 됩니다.

이 경로의 다음 단계인 실제 호스트에 붙이기 는 이 서버를 VS Code, Claude Code, Cursor 에 등록해 주고, 도구 는 도구가 무엇을 반환할 수 있는지를 더 깊게 다룹니다.

요약

  • registerTool(name, config, handler) 로 도구를 등록하고, inputSchema 는 여러분이 쓰는 유일한 Zod 스키마예요.
  • SDK는 모든 호출을 그 스키마로 검증해서, 핸들러 실행 전에 잘못된 인자를 거부합니다.
  • serveStdio(createServer) 는 팩토리에서 서버를 만들어 stdin/stdout 위에서 서빙해요.
  • stdout은 프로토콜을 싣고, 로그는 stderr로 남겨야 합니다.
  • npx @modelcontextprotocol/inspector <command> 는 호스트 없이도 어떤 stdio 서버든 연습해 볼 수 있어요.