LLM 에이전트를 위한 분산 트레이싱 (Distributed Tracing): MCP가 도구 호출을 관찰 가능하게 만드는 방법
요약
LLM 에이전트의 실행 루프에서 발생하는 도구 호출과 결정 과정을 관찰하기 위한 분산 트레이싱의 중요성을 다룹니다. MCP(Model Context Protocol)를 활용하여 호스트와 도구 서버 간의 인과 관계를 OpenTelemetry 표준으로 통합하는 방법을 제시합니다.
핵심 포인트
- 에이전트 시스템은 결정론적 호출이 아닌 확률적 루프 구조를 가짐
- 단순 로그 기록만으로는 도구 호출의 인과 관계 파악이 어려움
- MCP는 도구 경계를 정의하여 분산 트레이싱의 통합 지점 제공
- OpenTelemetry를 통해 프로세스 경계를 가로지르는 가시성 확보 가능
애플리케이션 관찰성 (Observability)이 확률적 에이전트 루프 (Stochastic agent loops)로 확장되는 방식 — 그리고 왜 도구 경계 (Tool boundary)가 중요한지에 대하여. LLM 시스템의 운영 환경에서의 장애는 종종 모델의 탓으로 잘못 돌려지곤 합니다. 실제로 많은 사고는 액션 레이어 (Action layer)에서 발생합니다: 타임아웃이 발생하는 다운스트림 API, 성공적인 RPC 내부에서 비즈니스 에러를 반환하는 도구, 혹은 호스트가 생성했지만 동일한 트레이스 (Trace)에 결합하지 않은 서브프로세스 등이 그 예입니다. 표준 로그는 완료 (Completions) 사항을 캡처하지만, 결정(decision) → 도구 호출(tool invocation) → 관찰(observation) → 다음 결정(next decision)으로 이어지는 인과 관계 체인 (Causal chain)을 보존하는 경우는 드뭅니다. 이 글은 바로 그 간극에 대해 다룹니다. 이 글은 클래식 APM과 에이전트 텔레메트리 (Agent telemetry)를 비교하고, Model Context Protocol (MCP)이 어떻게 관찰성에 안정적인 통합 지점을 제공하는지 설명하며, 호스트와 도구 서버가 하나의 trace_id를 공유하는 최소한의 참조 스택 (OpenTelemetry, 선택 사항인 Logfire, Jaeger)을 제시합니다. 참조 구현체: github.com/ekb-dev-ai/mcp-trace-demo
LLM 텔레메트리 vs 클래식 APM — 그리고 MCP가 전달하는 것
클래식 APM은 대체로 결정론적인 호출 그래프 (Call graph)를 가정합니다: 요청이 서비스에 진입하여 데이터베이스와 큐로 확산(fan out)되며, 각 홉 (Hop)은 안정적인 식별자, 지연 시간 (Latency), 그리고 에러 의미론 (Error semantics)을 가진 스팬 (Span)이 됩니다. 분석의 단위는 요청 경계 (Request boundary)입니다. OpenTelemetry가 성공한 이유는 그 그래프가 유한하고 배포 전반에 걸쳐 반복 가능하기 때문입니다.
에이전트 시스템은 작업의 형태를 변화시킵니다. 실행은 핸들러 (Handler)가 아니라 루프 (Loop)입니다: 런타임은 언어 모델을 호출하고, 구조화된 액션을 파싱하며, 외부 기능을 호출하고, 결과를 컨텍스트 (Context)에 추가한 뒤, 이 과정을 반복할 수 있습니다. 비용이 많이 들고 위험한 단계는 추론 (Inference, 가변적인 지연 시간 및 토큰 비용)과 부수 효과 (Side effects, 도구, API, 서브프로세스)입니다. 오직 "에이전트"만을 감싸거나 최종 텍스트만을 기록하는 트레이스는 다음과 같은 운영 질문에 답할 수 없습니다: 어떤 도구가 어떤 인자 (Arguments)와 함께 실행되었는지, 얼마나 걸렸는지, 그리고 실패가 전송 계층 (Transport-level)의 문제였는지 아니면 의미론적 (Semantic) 문제였는지에 대한 질문들입니다. 두 가지 부분적인 해결책이 흔히 사용되지만 둘 다 불완전합니다: 완료 로그 기록 (Completion logging) — 감사 (Auditable)는 가능하지만 도구의 인과 관계와는 분리되어 있습니다.
에이전트를 둘러싼 단일 루트 스팬 (Single root span) — Jaeger의 박스 하나로 표시되며, 도구 하위 프로세스(subprocess)나 원격 서버에 대한 가시성이 없습니다. 우리에게 필요한 것은 마이크로서비스가 이미 사용하고 있는 것과 동일한 추상화, 즉 프로세스 경계를 가로지르는 분산 트레이싱 (Distributed tracing)이며, 그 내부에 LLM 전용 스팬 (Spans)이 중첩되는 방식입니다. MCP는 OpenTelemetry를 대체하는 것이 아니라, 도구의 경계(tool boundary)가 어디에 위치하는지를 정의합니다. 이 프로토콜은 발견 (discovery), 타입화된 도구 스키마 (typed tool schemas), 그리고 호출 (invocation, tools/call)을 명시합니다. SEP-414를 통해 params._meta 내에서 W3C 트레이스 컨텍스트 (trace context)를 사용할 수 있으므로, 서비스 메시 (service mesh)를 가로지르는 방식과 동일하게 stdio 파이프나 HTTP를 통해 전파 (propagation)될 수 있습니다. 로컬 하위 프로세스이든 원격 호스트이든 MCP 서버는 관찰 가능성 (observability) 관점에서 동등한 피어 서비스 (peer service)입니다. 즉, 자체적인 service.name과 스팬을 가지며, trace_id를 통해 호스트와 결합될 수 있습니다. 호스트는 오케스트레이션 (orchestration)과 모델 라운드 (model rounds)를 기록하고, 서버는 도구 실행을 기록하며, 익스포터 (exporters)는 이를 하나의 워터폴 (waterfall)로 병합합니다. 요약하자면, HTTP가 APM에 서비스 간 호출을 위한 안정적인 와이어 포맷 (wire format)을 제공했다면, MCP는 APM에 모델-도구 간 호출을 위한 안정적인 와이어 포맷을 제공합니다. 에이전트 루프는 확률적 (stochastic)인 상태로 유지되지만, 실행 단계 (act step)는 검사 가능한 상태가 됩니다.
유용한 에이전트 트레이스가 포함하는 내용
특정 도메인에 국한하지 않고, 하나의 에이전트 실행을 위한 최소한의 유용한 트레이스에는 일반적으로 다음이 포함됩니다:
| 계층 (Layer) | 스팬 유형 (Span types, 예시) | 답변 가능한 질문 |
|---|---|---|
| 오케스트레이션 워크플로, 작업, 에이전트 | Orchestration workflow, task, agent | 어떤 프로그램이 어떤 순서로 실행되었는가? |
| 추론 (Inference) | LLM / completion 스팬 | 모델 라운드가 몇 번이었으며, 얼마나 느렸는가? |
| MCP 클라이언트 | 호스트에서의 tools/call | 어떤 도구가 언제 선택되었는가? |
| MCP 서버 | handle request, 도구 내부 스팬 | 도구가 실제로 무엇을, 얼마나 오래 수행했는가? |
운영상의 이점은 귀속 (attribution)입니다. 즉, "잘못된 추론"과 "느린 의존성", 그리고 "모델이 잘못 읽은 도구의 에러 페이로드 (error payload)"를 구분할 수 있게 됩니다.
아키텍처 패턴 (호스트 + MCP 서버)
일반적인 배포 패턴:
- 호스트 프로세스 (Host process) — 에이전트 프레임워크, 모델 클라이언트, MCP 클라이언트. 스팬을 예:
agent-host로 익스포트합니다. - 도구 프로세스 (Tool process) — 하나 이상의 도구를 노출하는 MCP 서버. 스팬을 예:
mcp-tool-server로 익스포트합니다.
전송 (Transport) — 주로 stdio (stdin/stdout을 통한 JSON-RPC 서브프로세스) 또는 원격 서버를 위한 HTTP를 사용합니다. 백엔드 (Backend) — Jaeger, Grafana Tempo, Logfire 등으로 전송되는 OTLP를 사용합니다.
┌──────────────┐ MCP (stdio 또는 HTTP) ┌─────────────────┐
│ agent-host │ ─── traceparent in _meta ──► │ mcp-tool-server │
│ LLM + client│ │ tool handlers │
└──────┬───────┘ └────────┬────────┘
│ OTLP │ OTLP
└────────────────────► trace backend ◄───────────┘
전파 (Propagation) 요구사항: 양측 모두 MCP를 계측 (instrument)하고 단일 TracerProvider (또는 호환 가능한 OTLP 파이프라인)를 공유해야 합니다. 호스트 (Host) 측에서는 프레임워크와 모델 계측기 (instrumentors)가 해당 프로바이더에 연결되어야 하며, 두 번째 전역 프로바이더를 설치해서는 안 됩니다. 그렇지 않으면 스팬 (spans)이 파편화됩니다.
비디오 워크스루 (Video walkthrough)
비디오에서는 위의 패턴을 엔드 투 엔드 (end-to-end)로 구현하고 UI에서 하나의 트레이스 (trace)를 살펴보는 과정을 보여줍니다.
구현 참고 사항 (레퍼런스 리포지토리 기준)
Stdio: stdout이 프로토콜입니다.
MCP가 stdio를 사용할 때, stdout은 오직 JSON-RPC만 전달해야 합니다. 자식 프로세스에서 stdout으로 출력하는 진단 라이브러리 (Diagnostic libraries)는 스트림을 손상시킵니다 ( Failed to parse JSONRPC message ). 해결 방법은 프로세스 범위 (process-scoped)로 설정하는 것입니다: 서버 프로세스에서 OTLP 익스포트 (export)는 활성화된 상태로 유지하되, 콘솔 익스포트는 비활성화합니다.
configure_telemetry ( service_name = " mcp-tool-server ", stdio_safe = True ) # → logfire.configure(..., console=False) # stdout 노이즈 없음; Jaeger는 변경 없음
두 프로세스 모두에서 공유되는 설정
def configure_telemetry (
*,
service_name : str,
instrument_crewai : bool = False,
stdio_safe : bool = False
):
logfire.configure (
service_name = service_name,
console = False if stdio_safe else None
)
logfire.instrument_mcp() # SEP-414 전파 + MCP 스팬 세맨틱스 (span semantics)
if instrument_crewai:
tp = logfire.DEFAULT_LOGFIRE_INSTANCE.config.get_tracer_provider()
CrewAIInstrumentor().instrument(tracer_provider = tp)
로컬 Jaeger (클라우드 토큰 없음): 기본 OTLP 엔드포인트 http://localhost:4318/v1/traces .
도구 핸들러(tool handlers) 내부의 스팬(Spans)
도구 코드는 프로토콜을 변경하지 않고도 MCP 핸들러 스팬 아래에 비즈니스 스팬(business spans)—지연 시간(latency), 도메인 오류(domain errors), 구조화된 속성(structured attributes)—을 추가할 수 있습니다:
with logfire.span("tool_operation", key=input_key):
result = do_work()
if result.is_business_failure:
logfire.error("domain failure", detail=result.detail)
JSON 내에서 RPC 성공 + 비즈니스 오류를 전송 실패(transport failure)와 구분하십시오. 이들은 백엔드와 SLO(Service Level Objective) 설계에서 다르게 나타납니다.
트레이스 읽기 (일반적인 방법)
- 호스트 서비스를 선택하고 최근 트레이스를 엽니다.
- 오케스트레이션(orchestration) → LLM 스팬(추론 라운드) 순으로 내려갑니다.
- 라운드 사이에 있는 MCP 클라이언트 도구/호출(tools/call) 스팬을 찾습니다.
- 도구 서버 서비스에서 동일한 trace_id를 필터링합니다. 서버 측 핸들러와 중첩된 도구 스팬이 시간상으로 일치하는지 확인합니다.
만약 클라이언트와 서버 트레이스가 trace_id를 공유하지 않는다면, 전파(propagation)가 끊어진 것입니다. 이는 주로 stdio 상의 stdout 오염, 두 번째 TracerProvider 사용, 또는 instrument_mcp() 누락으로 인해 발생합니다.
참조 데모 (선택 사항)
연결된 리포지토리는 제품 아키텍처가 아닌 토이 버티컬 슬라이스(toy vertical slice)입니다: 호스트에는 CrewAI + Ollama, stdio에는 FastMCP, 세 개의 도구, 그리고 Jaeger에서 가시적인 지연 시간을 확인하기 위한 인위적인 느린 경로(slow path)가 포함되어 있습니다. 이 데모는 추상적인 패턴을 한 번의 명령으로 재현할 수 있도록 존재합니다:
docker compose up -d && poetry install && ./scripts/demo.sh # UI: http://localhost:16686
이를 사용하여 익스포터(exporter)와 전파(propagation)를 검증하십시오. 프레임워크와 도구를 귀하의 스택으로 교체하더라도 관찰 가능성(observability) 모델은 동일하게 유지됩니다.
한계점
- 트레이싱은 무엇이 실행되었는지를 설명할 뿐, 출력이 올바른지 여부는 설명하지 않습니다 (평가(evals)는 여전히 필요합니다).
- 높은 카디널리티(High-cardinality)를 가진 프롬프트 콘텐츠는 비식별화(redaction)가 필요할 수 있습니다 (
TRACELOOP_TRACE_CONTENT=false또는 그에 상응하는 설정). - HTTP MCP는 stdio 데모에서 생략된 인증(auth), TLS 및 테넌시(tenancy) 문제를 추가합니다.
- 프레임워크 + 모델 + MCP + 수동 스팬에서 발생하는 스팬 카디널리티(Span cardinality)는 무거울 수 있으므로, 대규모 환경에서는 샘플링(sampling)이 필요할 수 있습니다.
요약
애플리케이션 관찰 가능성은 요청 범위의 결정론적 그래프(request-scoped, deterministic graphs)를 중심으로 성숙해 왔습니다. LLM 에이전트는 외부 액션이 포함된 확률적 루프(stochastic loops)를 도입합니다.
MCP는 이러한 액션들을 서비스 전반에 걸친 프로토콜 레벨의 호출 (protocol-level calls)로 표준화하며, SEP-414는 해당 경계를 가로질러 트레이스 컨텍스트 (trace context)를 전달하여 기존의 OpenTelemetry 파이프라인 (pipelines)을 적용할 수 있게 합니다. 엔지니어링 작업은 대부분 연결 (wiring) 작업입니다. 즉, 프로세스당 하나의 텔레메트리 (telemetry) 설정, MCP 인스트루멘테이션 (instrumentation), 올바른 stdio 규율, 그리고 호스트 (host)와 서버 (server) 스팬 (spans)을 하나의 ID 아래에 표시할 수 있는 백엔드 (backend)를 구축하는 것입니다. 코드 및 영상: mcp-trace-demo . MCP 트레이싱 (tracing)을 위한 프로덕션 패턴 (production patterns)에 대한 의견을 환영합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기