본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 01. 19:30

운영 환경에서 장애가 발생하기 전 TypeScript MCP 서버를 테스트하는 방법

요약

TypeScript MCP 서버를 운영 환경에 배포하기 전 발생할 수 있는 네트워크 오류, 세션 상태 유실, 동시성 문제 등을 방지하기 위한 테스트 전략을 다룹니다. 데모 수준의 작동을 넘어 실제 엣지 케이스를 검증하는 테스트 플레이북을 제안합니다.

핵심 포인트

  • 운영 환경의 엣지 케이스(재연결, 동시 호출 등) 검증 필요성
  • 전송 계층(Transport) 동작 및 HTTP 엔드포인트 테스트 중요성
  • 세션 상태 유지 및 인스턴스 확장 시 데이터 유실 주의
  • 도구 입력 스키마 및 출력 형태에 대한 계약 테스트 수행

당신의 MCP 서버는 노트북에서 잘 작동합니다. 도구 호출(tool calls)은 올바른 형태를 반환하고, 클라이언트는 깔끔하게 연결되며, 세션은 정상적으로 동작합니다. 그러다 배포를 하면, 네트워크 일시 오류(network hiccup) 이후 클라이언트가 재연결될 때 세션 상태(session state)가 사라져 버립니다. 혹은 인스턴스를 두 개로 확장했더니 세션 ID가 잘못된 프로세스로 해석되어 요청의 절반이 실패하기도 합니다. 또는 누군가 두 개의 동시 요청(concurrent requests)을 보내 도구 핸들러(tool handler)가 공유 상태(shared state)를 손상시키기도 합니다.

테스트는 사용자가 문제를 발견하기 전에 이러한 문제들을 잡아냅니다. 이것은 공식 SDK를 기반으로 구축된 TypeScript MCP 서버를 위한 테스트 플레이북(testing playbook)입니다.

MCP 서버의 데모와 운영 환경 사이의 간극

공식 TypeScript SDK는 무언가를 작동하게 만드는 것을 쉽게 만들어 줍니다. 몇 가지 도구 등록(tool registrations), McpServer 인스턴스, 전송 계층(transport)만 있으면 바로 서비스를 제공할 수 있습니다. 문제는 데모 수준에서의 "작동"과 운영 환경에서의 "작동"이 서로 다른 의미라는 점입니다.

데모는 하나의 해피 패스(happy path)만을 테스트합니다. 운영 환경은 실제 클라이언트로부터 발생하는 엣지 케이스(edge cases)를 테스트합니다: 재연결(reconnects), 동시 도구 호출(concurrent tool calls), 잘못된 형식의 입력(malformed inputs), 느린 다운스트림 API(slow downstream APIs), 그리고 전송 계약(transport contract) 자체 등이 그것입니다. 이러한 것들은 로컬 인스턴스에서 수동으로 한 번 실행하는 것만으로는 전혀 나타나지 않습니다.

이 간극은 SDK에 대한 비판이 아닙니다. 이는 SDK가 무엇이 서버를 망가뜨릴지에 대해 고민하지 않고도 서버를 구축할 수 있게 너무나 쉽게 만들어 준 결과입니다. 테스트 스위트(test suite)는 제품을 출시하기 전에 이 간극을 메워줍니다.

실제로 무엇이 망가지는가: 전송, 세션, 도구 계약

가장 자주 실패하는 세 가지 범주가 있습니다.

전송 동작 (Transport behavior). SDK는 버전 1.10.0에서 Streamable HTTP 지원을 추가했습니다. 이 전송 계층 하에서 서버는 POST와 GET을 모두 처리하는 단일 HTTP 엔드포인트를 노출합니다. 클라이언트는 도구 호출에는 POST를 사용하고, 서버 전송 이벤트(server sent events)를 통해 스트리밍 연결을 열 때는 GET을 사용합니다. stdio만을 사용하는 테스트는 이 부분을 완전히 놓칩니다.

세션 상태 (Session state). StreamableHTTPServerTransport는 세션당 상태를 유지(stateful)합니다. 만약 세션 ID를 키로 하여 프로세스 메모리에 무언가를 저장한다면, 재시작하거나 두 번째 인스턴스가 생길 때 해당 데이터는 사라집니다. 재연결을 시뮬레이션하지 않는 테스트는 이러한 실패 모드(failure mode)를 놓치게 됩니다.

도구 계약 (Tool contracts). 등록하는 각 도구는 입력 스키마 (input schema)와 예상되는 출력 형태 (expected output shape)를 가집니다. 유효한 입력값으로만 도구를 호출하는 테스트는 클라이언트가 약간 잘못된 형태를 보내거나 다운스트림 API (downstream API)가 예상치 못한 값을 반환하는 경우를 놓치게 됩니다.

도구 및 리소스를 격리하여 단위 테스트하기

가장 깔끔한 시작 지점은 전송 (transport) 단계가 개입하기 전, 도구 핸들러 (tool handler) 자체입니다.

각 도구 핸들러는 검증된 입력을 받아 결과를 반환하는 함수입니다. 테스트에서 직접 호출할 수 있도록 server.tool() 등록 코드에서 핸들러 로직을 추출하세요.

// tool-handlers.ts
export async function getItemHandler(input: { id: string }) {
  const item = await fetchItem(input.id);
...
// getItem.test.ts
import { getItemHandler } from "./tool-handlers";

...

이 패턴을 사용하면 전체 서버를 구동하지 않고도 각 핸들러를 독립적으로 테스트할 수 있습니다. 테스트 프레임워크의 모킹 (mocking) 유틸리티를 사용하여 다운스트림 호출을 스텁 (stub) 처리하세요. 정상 경로 (happy path), 잘못된 형식의 입력, 그리고 다운스트림 실패를 모두 다루어야 합니다.

리소스 핸들러 (Resource handlers)도 동일한 방식으로 작동합니다. 추출하고, 격리하여 테스트하며, 의존성을 스텁 처리하세요.

MCP 스키마에 대한 계약 검증 (Contract assertions)

단위 테스트 이후의 다음 단계는 도구 등록이 MCP 프로토콜을 준수하는지 확인하는 것입니다. 계약 테스트 (contract test)는 실제 서버를 인스턴스화하고 실제 프로토콜 요청을 보낸 다음, 응답 형태를 검증합니다.

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createServer } from "./server";
...

SDK의 InMemoryTransport는 바로 이 용도로 설계되었습니다. 네트워크 없이 동일한 프로세스 내에서 클라이언트와 서버를 실행할 수 있게 해주어, 테스트를 빠르고 결정론적 (deterministic)으로 유지해 줍니다.

각 도구에 대해 입력 스키마, 출력 콘텐츠 타입 (output content types), 에러 응답 형식 등 전체 응답 형태를 검증하세요. 이 단계는 서버가 지원한다고 주장하는 내용과 실제로 반환하는 내용 사이의 간극을 잡아내는 계층입니다.

스트리밍 가능한 HTTP 동작 테스트하기 (Testing Streamable HTTP behavior)

인메모리 전송 (in memory transport) 방식은 프로토콜 계층을 다룹니다. HTTP 전송 계층을 테스트하면 인증 미들웨어 (auth middleware), 세션 헤더 처리 (session header handling), 그리고 스트리밍 경로 (streaming path)와 같은 다른 종류의 장애를 포착할 수 있습니다.

임의의 포트에서 실제 HTTP 서버를 구동하고, 해당 서버를 대상으로 요청을 실행한 뒤, 각 테스트가 끝나면 서버를 종료하세요.

import { createServer as createHttpServer } from "http";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";

...

동일한 엔드포인트에 GET 요청을 열어 SSE 연결이 수락되는지 확인하는 테스트를 추가하세요. 또한, 잘못된 세션 ID (invalid session ID)를 전송했을 때 서버가 충돌 없이 이를 처리하는지 확인하는 테스트를 추가하세요.

회귀를 잡아내는 CI 설정 (A CI setup that catches regressions)

테스트 스위트 (test suite)는 일관되게 실행될 때만 도움이 됩니다. MCP 서버를 위한 최소한의 CI 설정은 다음과 같습니다:

  • 모든 커밋 시 유닛 테스트 (Unit tests) 실행 (빠름, 네트워크 미사용)
  • 모든 커밋 시 InMemoryTransport를 통한 계약 테스트 (Contract tests) 실행 (여전히 빠름)
  • 풀 리퀘스트 (pull requests) 및 메인 브랜치 병합 시 HTTP 전송 테스트 실행

만약 여러 인스턴스에 걸쳐 배포한다면, 두 개의 서버 프로세스를 시작하여 인스턴스 1에서 생성된 세션이 인스턴스 2에서 재개될 수 있는지 검증하는 테스트를 추가하세요. 이는 외부 세션 저장소 (external session store)를 테스트합니다. 이 테스트는 속도가 느리므로 풀 리퀘스트 단계에서 실행하는 것이 합리적입니다.

만약 MCP 서버에 의존하는 AI 제품을 구축하고 있다면, Next.js for AI products에 설명된 배포 및 관찰성 (observability) 패턴을 서버 상단의 프로덕션 계층에 직접 적용할 수 있습니다. 엔터프라이즈 세션 모델에 대해서는 MCP for enterprise agents를 참조하세요.

FAQ

MCP 서버란 무엇인가요?
MCP 서버는 Model Context Protocol을 통해 LLM 클라이언트에 도구 (tools), 리소스 (resources), 프롬프트 (prompts)를 노출합니다. 클라이언트는 서버에 연결하여 도구 이름을 호출하고 구조화된 결과를 받습니다.

MCP 서버를 어떻게 테스트하나요?
각 도구 핸들러 (tool handler)를 격리하여 단위 테스트 (unit tests)를 수행하는 것부터 시작하세요. InMemoryTransport를 사용하여 계약 테스트 (contract tests)를 추가합니다. 전체 네트워크 경로를 확인하려면 로컬 서버 인스턴스를 대상으로 HTTP 전송 (HTTP transport) 테스트를 추가하세요.

MCP는 어떤 전송 (transport) 방식을 사용하나요?
MCP는 로컬 사용을 위한 stdio와 네트워크 서버를 위한 Streamable HTTP를 지원합니다. Streamable HTTP는 도구 호출을 위한 POST 요청과 SSE 스트리밍을 위한 GET 요청을 처리하는 단일 엔드포인트 (endpoint)를 사용합니다. TypeScript SDK는 1.10.0 버전부터 Streamable HTTP를 지원합니다.

프로덕션 AI 시스템을 구축 중이신가요? 저는 mudassirkhan.me에서 에이전틱 AI (agentic AI) 및 AI 제품 엔지니어링 (AI product engineering) 컨설팅을 제공합니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0