🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

MCP 서버 구현 STDIO 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2026. 2. 1. · 7 Views

MCP 서버 구현 STDIO 완벽 가이드

Model Context Protocol(MCP) 서버를 STDIO 방식으로 구현하는 방법을 다룹니다. 프로세스 간 통신부터 JSON-RPC 프로토콜, CLI 도구 통합까지 실무에서 바로 활용할 수 있는 내용을 담았습니다.


목차

  1. STDIO_프로토콜_이해
  2. 프로세스_간_통신
  3. JSON_RPC_over_STDIO
  4. CLI_도구_통합
  5. 디버깅_전략
  6. 실전_CLI_기반_MCP_서버

1. STDIO 프로토콜 이해

김개발 씨는 요즘 핫한 AI 에이전트 개발에 뛰어들었습니다. Claude나 GPT 같은 LLM과 외부 도구를 연결하려면 MCP 서버가 필요하다고 합니다.

그런데 문서를 읽다 보니 STDIO라는 단어가 계속 등장합니다. "이게 대체 뭐지?"

STDIO는 Standard Input/Output의 약자로, 프로그램이 데이터를 주고받는 가장 기본적인 방법입니다. 마치 전화 통화처럼, 한쪽에서 말하면(stdout) 다른 쪽에서 듣고(stdin), 문제가 생기면 따로 알려주는(stderr) 구조입니다.

MCP 서버에서는 이 단순한 방식을 활용해 AI 모델과 도구 사이의 통신을 구현합니다.

다음 코드를 살펴봅시다.

import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';

// MCP 서버 인스턴스 생성
const server = new Server(
  { name: 'my-first-mcp', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

// STDIO 전송 계층 설정
const transport = new StdioServerTransport();

// 서버와 전송 계층 연결
await server.connect(transport);
// 이제 stdin으로 요청을 받고 stdout으로 응답합니다

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 팀에서 AI 기반 개발 도구를 만들기로 했고, MCP 서버 구현을 맡게 되었습니다.

공식 문서를 펼쳐 보니 첫 페이지부터 STDIO라는 용어가 등장합니다. "STDIO가 뭔지는 알겠는데, 왜 굳이 이걸 쓰는 거죠?" 김개발 씨가 선배 박시니어 씨에게 물었습니다.

박시니어 씨가 미소를 지으며 설명을 시작합니다. "STDIO를 이해하려면 먼저 프로그램이 어떻게 대화하는지 알아야 해요." 쉽게 비유하자면, STDIO는 마치 전화 통화와 같습니다.

전화기에는 마이크와 스피커가 있죠. 마이크로 말하고, 스피커로 듣습니다.

프로그램도 마찬가지입니다. stdin은 귀(입력 받기), stdout은 입(출력 하기), stderr은 긴급 알림(에러 출력)입니다.

그렇다면 왜 MCP는 HTTP나 WebSocket 대신 STDIO를 선택했을까요? 첫 번째 이유는 단순함입니다.

네트워크 설정이 필요 없습니다. 포트 충돌도 없고, 방화벽 문제도 없습니다.

그냥 프로그램을 실행하면 바로 통신이 시작됩니다. 두 번째는 보안입니다.

STDIO 통신은 로컬 프로세스 간에만 이루어집니다. 외부 네트워크에 노출될 위험이 없습니다.

AI 도구가 민감한 코드베이스에 접근할 때 이 점이 특히 중요합니다. 세 번째는 CLI 도구와의 자연스러운 통합입니다.

터미널에서 실행하는 모든 프로그램은 이미 STDIO를 사용합니다. MCP 서버도 하나의 CLI 프로그램처럼 동작하면 됩니다.

위 코드를 살펴보겠습니다. 먼저 Server 클래스로 MCP 서버 인스턴스를 만듭니다.

서버 이름과 버전, 그리고 제공할 기능(capabilities)을 정의합니다. 다음으로 StdioServerTransport를 생성합니다.

이 객체가 실제로 stdin에서 데이터를 읽고 stdout으로 응답을 보내는 역할을 합니다. 마지막으로 connect 메서드로 둘을 연결합니다.

이 순간부터 서버는 입력을 기다리는 상태가 됩니다. 실제 현업에서는 어떻게 활용할까요?

Claude Desktop이나 VS Code의 AI 확장 프로그램을 생각해보세요. 이들은 MCP 서버를 자식 프로세스로 실행하고, STDIO를 통해 명령을 주고받습니다.

사용자가 "이 파일 분석해줘"라고 하면, 그 요청이 STDIO를 통해 MCP 서버로 전달되는 것입니다. 주의할 점도 있습니다.

STDIO는 텍스트 기반이라는 것을 잊으면 안 됩니다. 바이너리 데이터를 직접 보내면 문제가 생길 수 있습니다.

또한 stdout으로 디버그 로그를 찍으면 통신이 깨집니다. 로그는 반드시 stderr로 보내야 합니다.

다시 김개발 씨 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 console.log 대신 console.error를 쓰라고 했던 거군요!"

실전 팁

💡 - stdout은 MCP 프로토콜 메시지 전용입니다. 디버그 출력은 반드시 stderr로 보내세요

  • StdioServerTransport는 자동으로 줄바꿈을 기준으로 메시지를 구분합니다

2. 프로세스 간 통신

김개발 씨가 STDIO의 개념을 이해하고 나니, 새로운 의문이 생겼습니다. "그런데 AI 클라이언트가 어떻게 제 MCP 서버를 실행하고 통신하는 거죠?" 이번에는 프로세스 간 통신의 비밀을 파헤쳐 봅시다.

**프로세스 간 통신(IPC)**은 서로 다른 프로그램이 데이터를 주고받는 방법입니다. MCP에서는 부모 프로세스(AI 클라이언트)가 자식 프로세스(MCP 서버)를 실행하고, 파이프를 통해 연결됩니다.

마치 부모가 아이에게 말을 걸고 대답을 듣는 것처럼, 두 프로세스가 대화를 나눕니다.

다음 코드를 살펴봅시다.

import { spawn } from 'child_process';

// MCP 서버를 자식 프로세스로 실행
const mcpServer = spawn('node', ['my-mcp-server.js'], {
  stdio: ['pipe', 'pipe', 'pipe']  // stdin, stdout, stderr 모두 파이프로 연결
});

// 서버에 요청 보내기 (부모 → 자식)
mcpServer.stdin.write(JSON.stringify({
  jsonrpc: '2.0',
  id: 1,
  method: 'initialize',
  params: { capabilities: {} }
}) + '\n');

// 서버로부터 응답 받기 (자식 → 부모)
mcpServer.stdout.on('data', (data) => {
  const response = JSON.parse(data.toString());
  console.error('서버 응답:', response);
});

박시니어 씨가 화이트보드에 그림을 그리기 시작했습니다. "프로세스 간 통신을 이해하려면, 먼저 운영체제가 프로그램을 어떻게 관리하는지 알아야 해요." 컴퓨터에서 실행 중인 프로그램을 프로세스라고 부릅니다.

여러분이 VS Code를 열고, 터미널을 열고, 브라우저를 열면 각각이 별도의 프로세스입니다. 이들은 서로 격리되어 있어서, 기본적으로는 서로의 존재를 모릅니다.

그런데 때로는 프로세스끼리 대화가 필요합니다. 마치 아파트에 사는 이웃처럼요.

각자 문을 닫고 살지만, 필요하면 인터폰으로 연락할 수 있습니다. 프로세스 세계에서 이 인터폰 역할을 하는 것이 **파이프(pipe)**입니다.

MCP 아키텍처에서는 AI 클라이언트가 부모 프로세스, MCP 서버가 자식 프로세스가 됩니다. 부모가 자식을 낳듯이, 클라이언트가 서버를 실행(spawn)합니다.

위 코드에서 spawn 함수를 보세요. 첫 번째 인자는 실행할 프로그램(node), 두 번째는 인자 목록(['my-mcp-server.js'])입니다.

세 번째 옵션의 **stdio: ['pipe', 'pipe', 'pipe']**가 핵심입니다. 이 설정은 자식 프로세스의 stdin, stdout, stderr을 각각 파이프로 연결하라는 의미입니다.

파이프는 단방향 통로입니다. 한쪽에서 쓰면 다른 쪽에서 읽을 수 있습니다.

mcpServer.stdin은 부모가 자식에게 말하는 통로입니다. write 메서드로 데이터를 보냅니다.

반대로 mcpServer.stdout은 자식이 부모에게 대답하는 통로입니다. on('data') 이벤트로 응답을 받습니다.

여기서 중요한 패턴이 있습니다. 메시지 끝에 \n(줄바꿈)을 붙이는 것입니다.

STDIO 통신에서는 줄바꿈이 메시지 구분자 역할을 합니다. 이걸 빼먹으면 서버가 메시지가 끝났는지 알 수 없어 계속 기다리게 됩니다.

실무에서 자주 겪는 문제가 있습니다. 바로 버퍼링입니다.

Node.js는 효율성을 위해 출력을 모았다가 한꺼번에 보내는 경우가 있습니다. 실시간 통신이 필요한 MCP에서는 이게 문제가 됩니다.

해결책은 간단합니다. 서버 측에서 매 메시지마다 flush를 해주거나, 처음부터 버퍼링을 끄면 됩니다.

MCP SDK의 StdioServerTransport는 이런 처리를 자동으로 해줍니다. 김개발 씨가 고개를 갸웃거립니다.

"그럼 stderr은 언제 쓰는 건가요?" 박시니어 씨가 대답합니다. "stderr은 대역 외 통신용이에요.

디버그 로그나 에러 메시지처럼, 실제 프로토콜 메시지가 아닌 것들을 보낼 때 씁니다."

실전 팁

💡 - 메시지 끝에 반드시 줄바꿈(\n)을 붙이세요. 이것이 메시지 경계를 나타냅니다

  • 자식 프로세스 종료 시 stdin.end()를 호출해 깔끔하게 정리하세요

3. JSON RPC over STDIO

이제 파이프가 연결되었습니다. 그런데 파이프로 무엇을 보내야 할까요?

김개발 씨가 코드를 살펴보니 JSON 형태의 메시지가 보입니다. "이게 바로 JSON-RPC인가요?" 맞습니다.

MCP의 심장부로 들어가 봅시다.

JSON-RPC는 JSON 형식으로 원격 프로시저 호출을 하는 프로토콜입니다. 클라이언트가 "이 함수를 이 인자로 실행해줘"라고 요청하면, 서버가 실행 결과를 돌려주는 구조입니다.

MCP는 이 JSON-RPC 2.0 명세를 STDIO 위에서 사용합니다. 간단하면서도 강력한 조합입니다.

다음 코드를 살펴봅시다.

// JSON-RPC 요청 메시지 구조
const request = {
  jsonrpc: '2.0',           // 프로토콜 버전 (필수)
  id: 1,                    // 요청 식별자 (응답 매칭용)
  method: 'tools/call',     // 호출할 메서드 이름
  params: {                 // 메서드에 전달할 파라미터
    name: 'read_file',
    arguments: { path: '/src/index.ts' }
  }
};

// JSON-RPC 응답 메시지 구조
const response = {
  jsonrpc: '2.0',
  id: 1,                    // 요청의 id와 동일해야 함
  result: {                 // 성공 시 result 필드
    content: [{ type: 'text', text: '파일 내용...' }]
  }
  // 실패 시 error: { code: -32600, message: '...' }
};

박시니어 씨가 커피를 한 모금 마시며 말했습니다. "프로그래밍에서 가장 어려운 것 중 하나가 뭔지 알아요?

바로 약속을 정하는 것이에요." 두 프로그램이 대화하려면 서로 어떤 형식으로 말할지 약속해야 합니다. "안녕"이라고 인사할지, "Hello"라고 할지, 아니면 "01001000"이라고 할지 정해야 하죠.

이런 약속을 프로토콜이라고 부릅니다. JSON-RPC는 그런 프로토콜 중 하나입니다.

이름에서 알 수 있듯이 JSON 형식을 사용하고, RPC(Remote Procedure Call, 원격 프로시저 호출) 패턴을 따릅니다. RPC는 마치 전화로 심부름을 시키는 것과 같습니다.

"김 대리, 3층 회의실 예약해줘"라고 전화하면, 김 대리가 예약하고 "완료했습니다"라고 대답합니다. 여러분은 직접 가지 않고도 일을 처리한 것입니다.

JSON-RPC 메시지에는 네 가지 필수 요소가 있습니다. 첫째, jsonrpc 필드입니다.

항상 "2.0"이어야 합니다. 이건 "나는 JSON-RPC 2.0 규격을 따르고 있어"라는 선언입니다.

둘째, id 필드입니다. 숫자나 문자열로 된 고유 식별자입니다.

왜 필요할까요? 여러 요청을 동시에 보낼 수 있기 때문입니다.

응답이 돌아왔을 때 어떤 요청에 대한 답인지 구분해야 합니다. 셋째, method 필드입니다.

호출하고 싶은 함수 이름입니다. MCP에서는 "tools/call", "resources/read" 같은 값이 들어갑니다.

넷째, params 필드입니다. 함수에 전달할 인자들입니다.

객체 형태로 전달합니다. 응답은 두 가지 경우가 있습니다.

성공하면 result 필드에 결과가 담깁니다. 실패하면 error 필드에 에러 정보가 담깁니다.

둘 중 하나만 있어야 합니다, 둘 다 있거나 둘 다 없으면 안 됩니다. MCP에서 특이한 점이 있습니다.

바로 **알림(notification)**입니다. id가 없는 메시지는 알림입니다.

응답을 기대하지 않는 일방적인 통보죠. "참고로 알려드립니다" 같은 메시지입니다.

김개발 씨가 질문합니다. "그럼 여러 요청을 한꺼번에 보낼 수도 있나요?" 박시니어 씨가 고개를 끄덕입니다.

"네, 배치 요청이 가능해요. 요청들을 배열로 묶어서 보내면 됩니다.

하지만 MCP에서는 잘 안 쓰여요. 대부분 하나씩 주고받거든요."

실전 팁

💡 - id는 요청마다 고유해야 합니다. 숫자를 1씩 증가시키는 방식이 일반적입니다

  • 알림(notification)은 id가 없으므로 응답이 오지 않습니다. 혼동하지 마세요

4. CLI 도구 통합

김개발 씨가 MCP 서버 구현을 마쳤습니다. 이제 실제로 AI 클라이언트와 연결해야 합니다.

"제 서버를 어떻게 등록하죠?" Claude Desktop 설정 파일을 열어보니 낯선 구조가 보입니다. CLI 도구로서 MCP 서버를 통합하는 방법을 알아봅시다.

MCP 서버는 CLI 도구로 동작합니다. Claude Desktop이나 다른 AI 클라이언트는 설정 파일에 서버 실행 명령을 등록해두고, 필요할 때 해당 명령을 실행합니다.

Node.js 서버라면 node 명령으로, Python 서버라면 python 명령으로 실행됩니다. 환경 변수와 작업 디렉토리 설정도 가능합니다.

다음 코드를 살펴봅시다.

// claude_desktop_config.json 예시
{
  "mcpServers": {
    "my-file-server": {
      "command": "node",
      "args": ["/path/to/my-mcp-server.js"],
      "env": {
        "DEBUG": "true",
        "API_KEY": "your-api-key"
      },
      "cwd": "/path/to/working/directory"
    },
    "python-server": {
      "command": "python",
      "args": ["-m", "my_mcp_server"],
      "env": {
        "PYTHONPATH": "/custom/path"
      }
    }
  }
}

박시니어 씨가 Claude Desktop 앱을 열었습니다. "자, 이제 만든 서버를 실제로 연결해볼까요?" MCP의 아름다운 점은 기존 도구 생태계와 자연스럽게 통합된다는 것입니다.

새로운 프레임워크를 배우거나 특별한 런타임을 설치할 필요가 없습니다. 그냥 CLI 프로그램을 만들면 됩니다.

마치 레고 블록과 같습니다. 규격만 맞으면 어떤 블록이든 끼울 수 있습니다.

MCP 서버도 STDIO 규격만 맞추면, Node.js든 Python이든 Go든 상관없이 연결됩니다. Claude Desktop의 설정 파일을 살펴보겠습니다.

macOS에서는 ~/Library/Application Support/Claude/claude_desktop_config.json 경로에 있습니다. mcpServers 객체 안에 서버들을 등록합니다.

키(my-file-server)는 서버의 식별자입니다. 클라이언트 UI에 이 이름이 표시됩니다.

command는 실행할 프로그램입니다. "node", "python", "npx" 같은 값이 들어갑니다.

args는 명령에 전달할 인자 배열입니다. env는 환경 변수입니다.

API 키나 설정 값을 여기로 전달합니다. 코드에 비밀 정보를 하드코딩하지 않아도 됩니다.

cwd는 작업 디렉토리입니다. 서버가 파일을 다룰 때 기준이 되는 경로입니다.

생략하면 클라이언트의 현재 디렉토리가 사용됩니다. 실무에서 자주 쓰는 패턴을 하나 소개하겠습니다.

npx를 활용한 실행입니다. 패키지를 전역 설치하지 않고도 실행할 수 있습니다.

json { "command": "npx", "args": ["-y", "@anthropic/mcp-server-filesystem"] } -y 플래그는 설치 확인 프롬프트를 자동으로 승인합니다. 이렇게 하면 누구나 별도 설치 없이 바로 서버를 사용할 수 있습니다.

김개발 씨가 설정을 저장하고 Claude Desktop을 재시작했습니다. 화면 하단에 연결된 서버 목록이 나타났습니다.

"와, 진짜 되네요!" 주의할 점이 있습니다. 경로를 잘못 쓰면 서버가 시작되지 않습니다.

특히 Windows에서는 역슬래시 문제가 자주 발생합니다. 가능하면 절대 경로를 사용하고, 슬래시(/)로 통일하는 것이 안전합니다.

실전 팁

💡 - 개발 중에는 DEBUG 환경 변수를 활용해 상세 로그를 활성화하세요

  • 설정 변경 후에는 반드시 AI 클라이언트를 재시작해야 적용됩니다

5. 디버깅 전략

김개발 씨의 MCP 서버가 연결되었습니다. 그런데 도구를 호출하면 아무 반응이 없습니다.

에러 메시지도 없습니다. "도대체 어디서 문제가 생긴 거지?" 투명한 파이프 속을 들여다볼 수 없어 답답합니다.

STDIO 기반 MCP 서버의 디버깅 기법을 알아봅시다.

STDIO 통신은 눈에 보이지 않아 디버깅이 까다롭습니다. stderr로 로그를 출력하고, MCP Inspector 도구로 메시지를 가로채 확인하는 것이 기본 전략입니다.

요청과 응답을 파일로 기록하는 래퍼 스크립트도 유용합니다. 체계적인 디버깅 습관이 개발 시간을 크게 단축시킵니다.

다음 코드를 살펴봅시다.

import { Server } from '@modelcontextprotocol/sdk/server/index.js';

// 디버그 로거 - 반드시 stderr 사용!
const debug = (message: string, data?: unknown) => {
  const timestamp = new Date().toISOString();
  console.error(`[${timestamp}] ${message}`, data ? JSON.stringify(data) : '');
};

const server = new Server(
  { name: 'debug-example', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

// 요청 처리 전후에 로깅
server.setRequestHandler('tools/call', async (request) => {
  debug('도구 호출 시작', request.params);
  try {
    const result = await handleTool(request.params);
    debug('도구 호출 성공', result);
    return result;
  } catch (error) {
    debug('도구 호출 실패', { error: error.message });
    throw error;
  }
});

박시니어 씨가 김개발 씨 옆에 앉았습니다. "STDIO 디버깅은 처음엔 막막해요.

하지만 요령을 알면 크게 어렵지 않습니다." STDIO 디버깅이 어려운 이유는 통신 채널이 숨겨져 있기 때문입니다. 웹 서버라면 브라우저 개발자 도구로 요청을 볼 수 있습니다.

하지만 STDIO는 프로세스 내부의 파이프라서 외부에서 볼 수 없습니다. 첫 번째 무기는 stderr 로깅입니다.

stdout은 프로토콜 메시지가 지나가는 신성한 통로입니다. 여기에 디버그 메시지를 섞으면 통신이 깨집니다.

반드시 stderr(console.error)를 사용해야 합니다. 로그를 찍을 때는 타임스탬프를 붙이세요.

메시지 순서와 시간 간격을 파악하는 데 큰 도움이 됩니다. 요청 ID도 함께 기록하면 어떤 요청에 대한 로그인지 추적할 수 있습니다.

두 번째 무기는 MCP Inspector입니다. Anthropic에서 제공하는 공식 디버깅 도구입니다.

서버와 클라이언트 사이에 끼어들어 모든 메시지를 가로채 보여줍니다. bash npx @anthropic/mcp-inspector node my-server.js 이 명령을 실행하면 웹 브라우저에 인스펙터 UI가 열립니다.

요청과 응답이 실시간으로 표시되고, 직접 테스트 요청을 보낼 수도 있습니다. 세 번째 무기는 래퍼 스크립트입니다.

서버를 직접 실행하는 대신, 모든 입출력을 파일에 기록하는 스크립트를 중간에 끼웁니다. bash #!/bin/bash tee /tmp/mcp-input.log | node my-server.js | tee /tmp/mcp-output.log 이렇게 하면 통신 내용이 파일에 남습니다.

나중에 천천히 분석할 수 있습니다. 흔히 겪는 문제 몇 가지를 정리해드립니다.

메시지가 전달되지 않는다: 줄바꿈(\n)을 빼먹었을 가능성이 높습니다. JSON 문자열 끝에 반드시 붙여주세요.

응답이 파싱되지 않는다: JSON이 깨졌거나, stdout에 다른 출력이 섞였을 수 있습니다. 모든 로그를 stderr로 옮기세요.

서버가 갑자기 종료된다: 처리되지 않은 예외(uncaught exception)가 원인일 수 있습니다. try-catch로 감싸고 에러를 로깅하세요.

김개발 씨가 로그를 확인하다가 환호성을 질렀습니다. "찾았다!

params 필드를 잘못 읽고 있었어요!"

실전 팁

💡 - console.log 대신 console.error를 쓰는 습관을 들이세요

  • MCP Inspector는 개발 필수 도구입니다. 북마크해두세요

6. 실전 CLI 기반 MCP 서버

이제 모든 조각이 맞춰졌습니다. 김개발 씨가 배운 것들을 종합해서 실제로 동작하는 MCP 서버를 만들어볼 시간입니다.

파일 시스템을 읽는 간단한 도구부터 시작해봅시다. 이 예제를 기반으로 여러분만의 MCP 서버를 확장해 나갈 수 있습니다.

실전 MCP 서버는 초기화 → 도구 등록 → 요청 처리 → 응답 반환의 흐름을 따릅니다. 도구를 정의할 때는 스키마를 명확히 하고, 에러 처리를 꼼꼼히 해야 합니다.

환경 변수로 설정을 주입받고, 적절한 로깅을 남기는 것이 운영 환경에서 중요합니다.

다음 코드를 살펴봅시다.

#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { readFile } from 'fs/promises';

const server = new Server(
  { name: 'file-reader', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

// 도구 목록 제공
server.setRequestHandler('tools/list', async () => ({
  tools: [{
    name: 'read_file',
    description: '파일 내용을 읽어옵니다',
    inputSchema: {
      type: 'object',
      properties: { path: { type: 'string', description: '파일 경로' } },
      required: ['path']
    }
  }]
}));

// 도구 실행
server.setRequestHandler('tools/call', async (request) => {
  if (request.params.name === 'read_file') {
    const content = await readFile(request.params.arguments.path, 'utf-8');
    return { content: [{ type: 'text', text: content }] };
  }
  throw new Error(`Unknown tool: ${request.params.name}`);
});

// STDIO 연결
const transport = new StdioServerTransport();
await server.connect(transport);

박시니어 씨가 키보드를 두드리며 말했습니다. "자, 이제 처음부터 끝까지 동작하는 서버를 만들어봅시다." 실전 MCP 서버의 구조는 크게 네 부분으로 나뉩니다.

첫째, 서버 인스턴스 생성입니다. Server 클래스를 생성할 때 서버 이름과 버전을 지정합니다.

이 정보는 클라이언트가 연결할 때 확인용으로 사용됩니다. capabilities에는 이 서버가 제공하는 기능 유형을 선언합니다.

도구를 제공하면 tools, 리소스를 제공하면 resources를 추가합니다. 둘째, 도구 목록 핸들러입니다.

"tools/list" 메서드는 이 서버가 제공하는 모든 도구 목록을 반환합니다. 각 도구에는 name, description, inputSchema가 필요합니다.

inputSchema가 특히 중요합니다. 이것이 AI 모델에게 "이 도구는 이런 입력을 받습니다"라고 알려주는 명세서입니다.

JSON Schema 형식을 따릅니다. 필수 필드(required)를 명확히 지정해야 AI가 올바른 형식으로 호출합니다.

셋째, 도구 실행 핸들러입니다. "tools/call" 메서드는 실제로 도구를 실행합니다.

request.params.name으로 어떤 도구가 호출되었는지 확인하고, request.params.arguments에서 인자를 꺼내 씁니다. 반환값의 형식도 정해져 있습니다.

content 배열 안에 결과를 담습니다. 텍스트면 type: "text", 이미지면 type: "image"입니다.

이 형식을 지켜야 AI 모델이 결과를 제대로 해석합니다. 넷째, 전송 계층 연결입니다.

StdioServerTransport를 생성하고 server.connect()를 호출하면 서버가 가동됩니다. 이 순간부터 stdin으로 들어오는 요청을 처리하기 시작합니다.

에러 처리도 빠뜨리면 안 됩니다. 알 수 없는 도구가 호출되면 에러를 던져야 합니다.

파일이 없거나 권한이 없을 때도 적절한 에러 메시지를 반환해야 합니다. 운영 환경에서는 몇 가지를 더 고려해야 합니다.

환경 변수로 설정 주입: API 키나 허용 경로 같은 설정은 코드에 넣지 말고 환경 변수로 받으세요. graceful shutdown: SIGTERM 시그널을 받으면 진행 중인 요청을 완료하고 깔끔하게 종료하도록 처리하세요.

타임아웃 처리: 도구 실행이 너무 오래 걸리면 적절히 중단해야 합니다. AI 클라이언트가 무한정 기다리지 않도록 합니다.

김개발 씨가 완성된 서버를 Claude Desktop에 연결했습니다. "파일 /src/index.ts 읽어줘"라고 물었더니, AI가 MCP 서버를 호출해 파일 내용을 가져왔습니다.

"드디어 완성이다!" 박시니어 씨가 어깨를 두드렸습니다. "이제 시작이에요.

여기서부터 여러분만의 도구를 추가해 나가면 됩니다. 데이터베이스 조회, API 호출, 코드 실행까지 가능성은 무궁무진합니다."

실전 팁

💡 - inputSchema를 상세히 작성하세요. AI 모델의 호출 정확도가 달라집니다

  • 첫 번째 줄의 shebang(#!/usr/bin/env node)으로 직접 실행 가능한 스크립트로 만드세요

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#MCP#STDIO#JSON-RPC#CLI#ProcessCommunication

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.