첫 MCP 서버 구축하기: 내부 도구를 AI 에이전트에게 안전하게 노출하기 (TypeScript)
요약
본 튜토리얼은 Model Context Protocol(MCP) 서버를 TypeScript로 구축하는 방법을 다루며, AI 에이전트에게 내부 도구를 안전하게 노출하는 방법을 안내합니다. MCP는 단일 서버로 여러 클라이언트가 공통으로 사용할 수 있게 하여 개발 편의성을 높입니다. 특히 엄격한 스키마 검증, 읽기 전용 원칙 준수, 감사 로그 기록 등 보안 패턴을 적용하는 것이 중요함을 강조합니다.
핵심 포인트
- MCP 서버는 단일 지점으로 여러 클라이언트가 공통 도구를 사용하게 합니다.
- 보안의 핵심은 '작동 여부'보다 '오작동 시 피해 최소화'에 있습니다.
- TypeScript와 Zod를 사용하여 엄격한 입력 스키마 검증을 구현해야 합니다.
- 모든 도구 호출에는 감사 로그(Audit Log)를 남겨 추적 가능성을 확보해야 합니다.
Model Context Protocol (MCP)은 AI 어시스턴트와 에이전트를 도구 및 데이터에 연결하는 기본 방식이 되었습니다. 모든 모델과 모든 클라이언트에 대해 사용자 지정 통합을 작성하는 대신, MCP 서버 하나만 작성하면 되고, 어떤 MCP 지원 클라이언트(데스크톱 어시스턴트, IDE, 에이전트 프레임워크)라도 해당 도구를 발견하고 호출할 수 있습니다.
이러한 편리함에는 이면이 있습니다. MCP 서버는 호출자가 언어 모델인 API이며, 이는 읽은 텍스트에 의해 혼란을 겪거나, 조작되거나, 단순히 틀릴 수 있기 때문입니다. 따라서 '작동한다'가 기준이 아닙니다. 기준은 '작동하며, 오작동하는 에이전트가 이를 통해 할 수 있는 최악의 일이 작고 눈에 보인다'는 것입니다.
본 튜토리얼에서는 TypeScript로 작은 MCP 서버를 구축하여 주문 조회(order lookup)와 환불 요청(refund request)이라는 두 가지 내부 기능을 노출하고, 처음부터 안전 패턴을 적용할 것입니다: 엄격한 입력 스키마, 기본적으로 읽기 전용, pending 요청만 생성하는 쓰기 작업, 그리고 모든 호출에 대한 감사 로그입니다.
MCP 서버가 실제로 노출하는 것들
MCP 서버는 클라이언트에게 세 가지 종류의 것을 제공할 수 있습니다:
- 도구(Tools): 모델이 호출할 수 있는 함수 (입력과 선택적으로 출력에 대한 JSON Schema 포함).
- 리소스(Resources): 클라이언트가 컨텍스트로 로드할 수 있는 읽기 전용 데이터이며, URI로 주소 지정됩니다.
- 프롬프트(Prompts): 사용자가 호출할 수 있는 재사용 가능한 프롬프트 템플릿.
통신은 JSON-RPC를 통해 이루어지며, 두 가지 표준 전송 방식 중 하나를 사용합니다: stdio (클라이언트가 로컬 서브프로세스로 서버를 실행) 또는 Streamable HTTP (서버가 원격 서비스로 실행). 우리는 개발하고 테스트하는 가장 빠른 방법인 stdio부터 시작한 다음, 원격 배포 시 변경되는 사항을 다룰 것입니다.
프로젝트 설정
mkdir internal-tools-mcp && cd internal-tools-mcp
npm init -y
npm install @modelcontextprotocol/sdk zod
...
package.json에 `
몇 가지 의도적인 선택 사항:
- 스키마는 제안이 아닌 계약입니다.
orderId에 대한 정규 표현식(regex)은 모델이*, SQL 조각, 또는 다른 고객의 이메일 주소를 전달할 수 없음을 의미합니다. SDK는 핸들러가 실행되기 전에 스키마를 사용하여 입력을 검증합니다. - 출력은 투영됩니다 (Projected). 우리는 주소나 결제 메타데이터가 포함된 전체 주문 기록이 아닌 네 개의 필드를 반환합니다. 사용자가 반환하는 모든 것은 모델의 컨텍스트에 들어가고, 거기서 사용자 또는 다른 도구로 전달될 수 있습니다.
- 오류는 예외(Exception)가 아니라 도구 결과입니다.
isError: true와 읽기 쉬운 메시지를 반환하면 모델이 전체 턴을 실패하는 대신 복구할 수 있게 합니다("해당 주문은 존재하지 않습니다. ID를 확인해 주시겠어요?"). - 어노테이션(Annotations)은 동작을 설명합니다.
readOnlyHint는 이 도구가 아무것도 수정하지 않음을 클라이언트에게 알려주며, 일부 클라이언트는 이를 사용하여 확인 프롬프트를 건너뜁니다. 어노테이션은 보안 경계가 아니라 클라이언트를 위한 힌트로 취급해야 합니다. - 로깅은 stderr로 전송됩니다. stdio 트랜스포트(transport)를 사용하는 경우, stdout 자체가 프로토콜 채널입니다. 임의의
console.log는 JSON-RPC 스트림을 손상시킵니다. 이것이 가장 흔한 초보자 오류입니다.
감사 로그 (The Audit Log)
모든 도구 호출은 기록을 남겨야 합니다. 나중에 쿼리할 수 있도록 구조화하여 유지하세요.
// src/audit.ts
import { appendFileSync } from "node:fs";
...
운영 환경(production)에서는 이러한 항목들을 로깅 플랫폼으로 전송하고 요청 ID 또는 세션 ID와 인증된 사용자 정보를 첨부해야 하지만, 단순히 로컬 JSONL 파일만 있어도 "어제 에이전트가 무엇을 했는지?"에 답하기에는 충분합니다.
스스로 피해를 입힐 수 없는 쓰기 도구 (A Write Tool That Can't Do Damage on Its Own)
흥미로운 부분입니다. 우리는 에이전트가 환불 처리를 돕도록 하길 원하지만, 모델이 직접 환불을 발행하는 것은 원하지 않습니다. 패턴은 다음과 같습니다: 도구(tool)가 임시 요청(pending request)을 생성하고, 이 요청은 사용자의 기존 시스템에서 사람이 승인합니다. index.ts 파일의 전송 라인 위에 다음 등록 코드를 추가하세요:
const MAX_REFUND_CENTS = 20_000; // 서버 측에서 강제되는 하드 상한선
server.registerTool(
...
프롬프트가 아닌 코드에서 강제되는 사항에 주목하십시오: 최대 금액, "주문 총액을 초과하지 않음" 규칙, idempotency(멱등성), 그리고 돈이 절대 움직이지 않는다는 사실입니다. 도구 설명은 모델이 잘 작동하도록 돕고; 핸들러(handler)는 잘못된 동작이 무해하다는 것을 보장합니다. 또한 응답에서 아무것도 지불되지 않았다고 명확하게 모델에 알려주기 때문에, 사용자에게 "환불이 완료되었습니다"라고 말하지 않는다는 점도 주목하십시오.
MCP Inspector를 사용한 테스트
서버에 대해 공식 인스펙터(inspector)를 빌드하고 실행하세요:
npx tsc
npx @modelcontextprotocol/inspector node build/index.js
인스펙터는 등록된 도구 목록을 보여주고, 해당 스키마를 표시하며, 수동으로 호출할 수 있게 해줍니다. 실제 모델을 연결하기 전에, 혼란스럽거나 조작된 에이전트가 보낼 수 있는 입력값들(잘못된 ID 형식, 음수 금액, 한도 초과 금액, 같은 환불 요청을 두 번 하는 경우, 극도로 긴 메모 등)을 시도해 보세요. 모든 케이스는 깔끔하게 실패하거나 무해해야 합니다.
그런 다음 클라이언트를 연결하세요. 대부분의 데스크톱 MCP 클라이언트는 다음과 같은 설정 항목을 사용합니다:
{
"mcpServers": {
"internal-tools": {
...
원격으로 이동하기: 변경되는 사항
stdio 서버는 실행한 사람의 권한으로 작동합니다. 팀을 위해 Streamable HTTP를 통해 배포할 경우, 실제 인증(authentication)과 인가(authorization)가 필요합니다:
- 모든 요청 인증(Authenticate every request). MCP 사양은 HTTP 전송을 위한 OAuth 2.1 기반 권한 부여 흐름을 정의하며, 이 과정에서 사용자의 서버는 리소스 서버 역할을 수행하고 ID 제공자(identity provider)가 발행한 접근 토큰을 검증합니다.
- 토큰 오디언스(token audience) 검증. 사용자의 MCP 서버를 위해 발행된 토큰만 허용해야 하며, 클라이언트의 토큰을 다운스트림 API로 전달해서는 안 됩니다 (사양은 이를 명시적으로 금지하는 "토큰 전달(token passthrough)" 방식을 사용합니다). 다운스트림 호출에는 자체 서비스 자격 증명 또는 적절한 토큰 교환 방식을 사용해야 합니다.
- 도구별 및 사용자별 권한 부여(Authorize per tool and per user). 스코프(scope)나 역할을 도구에 매핑해야 합니다. 지원 에이전트의 세션은
lookup_order와request_refund를 얻을 수 있지만, 분석가(analyst)의 세션은 읽기 전용 도구만 얻을 수 있을 수 있습니다. - 호출자에게 데이터 범위 제한(Scope data to the caller).
lookup_order는 인증된 사용자가 볼 수 있도록 허가된 주문만을 반환해야 합니다. 에이전트는 사용자 권한을 상속받으며, 그 이상은 절대 가질 수 없습니다. - 속도 제한 및 시간 초과 설정(Rate-limit and time out). 에이전트가 루프를 돌릴 수 있으므로, 세션당 호출 횟수와 분당 호출 횟수를 제한하고 백엔드 호출에 대한 시간 초과를 설정해야 합니다.
- 테스트 시
Origin헤더를 검증하고 로컬에서 바인딩하여 DNS 리바인딩 공격(DNS-rebinding attacks)을 방지해야 합니다.
MCP에 특화된 보안 위험 요소
- 도구 결과를 통한 프롬프트 주입 (Prompt injection through tool results). 도구가 사용자 생성 텍스트(티켓 본문, 이메일, 웹 페이지 등)를 반환하는 경우, 해당 텍스트에 모델을 대상으로 하는 지침이 포함될 수 있습니다. 도구의 출력이 모델과 유해한 행동 사이를 막는 유일한 장벽이 되도록 절대 두지 말고, 핸들러에서 제한 사항을 강제해야 합니다.
- 도구 오염 (Tool poisoning). 도구 설명은 모델에 의해 읽힙니다. 신뢰하는 서버만 설치하고, 설명을 검토하며, 타사 서버의 버전을 고정(pin)하십시오.
- 지나치게 광범위한 도구 (Over-broad tools).
run_sql또는call_any_api와 같은 도구는 귀하의 서버를 모델을 위한 원격 셸로 만듭니다. 일반적인 도구 하나보다 여러 개의 좁은(narrow) 도구를 선호하십시오. - 유출되는 출력 (Leaky outputs). 최소한만 반환하십시오. 에이전트가 필요로 하지 않는 개인 데이터는 마스킹(redact) 처리해야 합니다.
- 설정 파일 내 비밀 정보 (Secrets in config). 자격 증명은 환경 변수나 시크릿 관리자(secrets manager)에 보관하고, 도구 설명이나 응답에는 절대 넣지 마십시오.
배포 전 체크리스트 (Checklist Before You Ship)
- 모든 도구는 엄격한 입력 스키마(형식, 열거형(enums), 최대 길이, 숫자 범위)를 가집니다.
- 출력은 에이전트가 필요로 하는 필드에만 투영됩니다 (projected).
- 기본적으로 읽기 전용이며, 각 쓰기 도구는 개별적으로 검토합니다.
- 결과를 초래하는 행동(consequential actions)은 보류 중인 요청을 생성하거나 승인을 필요로 합니다.
- 제한 사항과 비즈니스 규칙은 프롬프트가 아닌 핸들러에서 강제됩니다.
- 모든 호출은 사용자, 도구, 인자(마스킹 처리됨), 결과와 함께 감사 로그(audit-logged)를 남깁니다.
- 원격 배포는 OAuth, 오디언스 검사(audience checks), 및 도구별 권한 부여를 사용합니다.
- 속도 제한(Rate limits)과 시간 초과(timeouts)가 마련되어 있습니다.
- 인스펙터에서 적대적이고 잘못된 입력으로 테스트했습니다.
마무리하며 (Wrapping Up)
MCP 서버는 작지만, 확률론적 시스템과 귀하의 실제 데이터 사이에 위치합니다. 이를 모든 공개 API처럼 설계하되, 한 가지 추가 가정을 합니다: 호출자가 당신이 의도하지 않은 무언가를 하도록 설득될 수 있다는 것입니다. 좁은 도구, 강제된 제한 사항, 기본적으로 보류되는 쓰기 작업, 그리고 좋은 감사 로그는 신뢰할 수 있는 에이전트 통합에 거의 다다르게 해줄 것입니다.
본문은 NextCTL LLC의 엔지니어링 팀에서 작성했습니다. NextCTL LLC는 AI 에이전트, MCP 통합(integrations), 그리고 미국, 캐나다, 영국 및 유럽에 있는 팀들을 위한 맞춤형 소프트웨어를 구축하는 AI 기반 소프트웨어 엔지니어링 및 비즈니스 자동화 회사입니다. 더 많은 정보는 nextctl.com에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기