
Go로 MCP 게이트웨이 구축하기: AI 에이전트와 JSON-RPC 도구 간의 가교 역할
요약
Anthropic의 MCP 서버와 HTTP 기반 AI 에이전트 간의 통신 격차를 해소하기 위해 Go 언어로 구축된 MCP 게이트웨이를 소개합니다. stdio 기반의 JSON-RPC 통신을 HTTP 프록시로 변환하여 관리 효율성과 관찰 가능성을 높이는 아키텍처를 다룹니다.
핵심 포인트
- MCP 서버(stdio/JSON-RPC)와 에이전트(HTTP) 간의 프로토콜 격차 해결
- 단일 HTTP 엔트리 포인트를 통한 중앙 집중식 도구 호출 및 라우팅
- Prometheus 메트릭 노출을 통한 프로덕션 수준의 관찰 가능성 확보
- 서브프로세스 관리 및 인메모리 레지스트리를 활용한 효율적인 설계
Anthropic의 Model Context Protocol (MCP)은 LLM 에이전트를 외부 도구와 연결하는 표준이 되어가고 있습니다. 하지만 격차가 존재합니다: MCP 서버는 stdio를 통해 JSON-RPC로 통신하는 반면, 에이전트와 오케스트레이터(orchestrators)는 보통 HTTP를 사용합니다.
저는 이 격차를 메우기 위해 mcp-gateway를 구축했습니다. 이는 MCP 서버를 등록하고, HTTP를 통해 도구 호출(tool calls)을 프록시하며, Prometheus 메트릭을 노출하는 프로덕션 지향적인 Go 서비스입니다. 이것이 어떻게 작동하는지, 그리고 왜 이러한 아키텍처적 선택을 했는지 설명하겠습니다.
문제점
MCP는 도구 상호 운용성(interoperability) 측면에서 매우 훌륭합니다. MCP 서버(파일 시스템, 데이터베이스, 웹 페치 등)를 설치하면, MCP 호환 에이전트라면 무엇이든 이를 사용할 수 있습니다. 하지만 다음과 같은 문제가 있습니다:
- 모든 에이전트에 MCP 클라이언트가 내장되어 있어야 함
- stdio를 통한 JSON-RPC는 디버깅과 모니터링이 어려움
- 중앙 집중식 상태 확인(health checks), 재시도(retries) 또는 메트릭이 없음
- 10개의 MCP 서버를 실행한다는 것은 관리해야 할 10개의 서브프로세스(subprocesses)가 있음을 의미함
저는 단일 HTTP 엔트리 포인트(entry point)를 원했습니다. 에이전트가 JSON을 보내면 게이트웨이가 적절한 MCP 서버로 라우팅하고, 저는 무료로 관찰 가능성(observability)을 얻는 구조 말입니다.
아키텍처
주요 설계 결정
1. 인메모리 레지스트리 (In-Memory Registry, MVP용)
MVP(Minimum Viable Product)를 위해 PostgreSQL 대신 인메모리 레지스트리를 선택했습니다. 이유는 다음과 같습니다:
- MCP 서버는 **서브프로세스(subprocesses)**입니다. 즉, 그 상태는 어차피 메모리에 존재합니다.
- 데이터베이스를 추가한다고 해서 장애 조치(failover) 문제가 해결되지는 않습니다 (게이트웨이가 충돌하면 서브프로세스도 종료됩니다).
- 실제 병목 지점은 레지스트리 조회(lookups)가 아니라 stdio IPC입니다.
- PostgreSQL은 멀티 테넌트(multi-tenant) 배포를 위한 로드맵에 포함되어 있습니다.
2. stdio 전송 (stdio Transport)
MCP 서버는 일반적으로 CLI 도구(npx, uvx)로 배포됩니다. stdio는 가장 호환성이 높은 전송 방식입니다. 게이트웨이는 다음과 같이 동작합니다:
- 등록 시 프로세스를 생성(spawns)합니다.
- stdin을 통해 JSON-RPC를 전송합니다.
- stdout에서 응답을 읽습니다.
- 종료 시 (3초의 유예 기간을 두고) 프로세스를 종료합니다.
3. 오류 의미론 (Error Semantics)
MCP는 전송 오류 (transport errors) (서버 다운, JSON-RPC 깨짐)와 도구 오류 (tool errors) (파일을 찾을 수 없음, 잘못된 쿼리)를 구분합니다:
- 전송/RPC 오류 → HTTP 502 + 로그 기록
- 도구 실행 오류 (
isError: true) → HTTP 200 + 메트릭mcp_tool_calls_total{status="error"}
이는 HTTP 프록시가 작동하는 방식과 유사합니다. 즉, 업스트림 도구가 실패하더라도 게이트웨이는 정상 상태를 유지합니다.
코드 (The Code)
선언적 설정 (Declarative Configuration)
# config/servers.yaml
servers:
- name: filesystem
...
HTTP 프록시 핸들러 (HTTP Proxy Handler)
func (h *Handler) CallTool(w http.ResponseWriter, r *http.Request) {
server := chi.URLParam(r, "name")
tool := chi.URLParam(r, "tool")
...
우아한 종료 (Graceful Shutdown)
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
...
관찰 가능성 (Observability)
# 도구 호출 볼륨 (Tool call volume)
curl -s http://localhost:8080/metrics | grep mcp_tool_calls_total
...
모든 요청은 slog에 요청 ID (request ID)가 주입되며, 컨텍스트 (context)를 통해 전파됩니다. 로그는 구조화된 JSON (structured JSON) 형식을 사용하므로, 운영 환경에서 정규 표현식 (regex)을 통한 파싱이 필요하지 않습니다.
실행하기 (Running It)
git clone https://github.com/kantik001/mcp-gateway.git
cd mcp-gateway
docker compose up --build -d
...
테스트 (Testing)
MCP 클라이언트 레이어는 모의 서브프로세스 (mock subprocess) 테스트를 포함하여 **70.6%의 테스트 커버리지 (test coverage)**를 확보하고 있습니다. CI 게이트 (CI gate) 통과 조건은 다음과 같습니다:
- 모든 테스트 통과
golangci-lint통과 (clean)internal/mcp디렉토리의 커버리지 ≥ 60%
make test
make coverage
make lint
향후 계획 (What's Next)
- 멀티 테넌트 (multi-tenant) 배포를 위한 Postgres 기반 레지스트리 (registry)
- Redis 도구 결과 캐시 (tool-result cache)
- OpenTelemetry 트레이스 (traces)
- 장시간 실행되는 도구를 위한 SSE 스트리밍 (SSE streaming)
링크 (Links)
- 코드: github.com/kantik001/mcp-gateway
- 라이선스: Apache 2.0
- 저는 Senior AI Infrastructure Engineer 직무(원격 / EU)에 열려 있습니다 — 이메일 보내기
LLM 에이전트를 외부 도구와 연결하기 위해 어떤 패턴을 사용하시나요? 댓글에서 함께 논의해 봅시다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기