관측성
관측성 (메트릭, 추적, 로그)
MCP 서버는 Prometheus 메트릭을 노출할 수 있고 OpenTelemetry 분산 추적과 로그 내보내기를 지원해요. OTel MCP 시맨틱 컨벤션을 따르죠.
메트릭은 SSE 또는 streamable-http 전송이 필요해요. 추적과 로그 내보내기는 표준 OTEL_* 환경 변수를 사용하고 어떤 전송에서든 --metrics와 무관하게 동작해요.
참고: mcp-grafana는 현재 추적과 로그 모두 OTLP/gRPC 전송만 지원해요. OTEL_EXPORTER_OTLP_PROTOCOL(그리고 _TRACES_PROTOCOL/_LOGS_PROTOCOL 변형)은 반영되지 않고, 어쨌든 gRPC가 사용돼요.
출처: 문서
본문
무엇을 얻을 수 있을까요
MCP 작업 메트릭(HTTP 전송에서만)을 스크래핑하고, stdio를 포함한 어떤 전송에서든 추적과 로그를 Tempo, Loki, Grafana Cloud로 내보낼 수 있어요.
시작하기 전에
- SSE 또는 streamable-http로 실행 중인 서버 (메트릭은 stdio에서 사용 불가).
Prometheus 메트릭 활성화하기
SSE 또는 streamable HTTP 전송을 쓸 때 --metrics로 Prometheus 메트릭을 활성화해요:
# Metrics on the main server at /metrics
./mcp-grafana -t streamable-http --metrics
# Metrics on a separate listen address
./mcp-grafana -t streamable-http --metrics --metrics-address :9090
사용 가능한 메트릭:
| Metric | Type | Description |
|---|---|---|
| mcp_server_operation_duration_seconds | Histogram | MCP operation duration (labels: mcp_method_name, gen_ai_tool_name, error_type, network_transport, mcp_protocol_version, and — for tools/call on selected tools — mcp_tool_operation, mcp_tool_resource_type, mcp_tool_phase) |
| mcp_server_session_duration_seconds | Histogram | MCP client session duration (labels: network_transport, mcp_protocol_version) |
| http_server_request_duration_seconds | Histogram | HTTP server request duration (from otelhttp) |
참고: 메트릭은 SSE 또는 streamable HTTP 전송을 쓸 때만 사용할 수 있어요. stdio 전송에서는 사용할 수 없어요.
도구 호출 차원 라벨
tools/call의 경우 mcp_server_operation_duration_seconds는 최대 세 개의 추가 저카디널리티 라벨을 담을 수 있어서, 호출이 무엇을 하고 있었는지에 따라 시간을 잘라낼 수 있어요:
| Label | Source | Notes |
|---|---|---|
| mcp_tool_operation | the tool’s operation argument | Multiplexer tools only (e.g. alerting_manage_rules); one of the tool’s declared operations, else other. |
| mcp_tool_resource_type | the tool’s type argument | e.g. the datasource plugin type on create_datasource; a plugin type the server ships a schema for, else other. |
| mcp_tool_phase | the tool’s result _meta | Phase of a multi-call flow (e.g. create_datasource schema guidance vs. actual creation). |
인자는 원시 클라이언트 입력이라, 인자에서 파생된 두 라벨은 도구별·값별로 허용 목록에 들어가요. 목록에 없는 도구는 둘 다 내보내지 않고, 목록에 없는 값은 하나의 other 버킷으로 합쳐져서 각 라벨의 시리즈 수가 고정돼요. 도구 검증만으로는 이것을 제한할 수 없어요 — 거부된 작업도 계측되고, create_datasource의 type은 자유 텍스트라 알 수 없는 값에서도 성공하니까요. mcp_tool_phase는 예외예요. 도구가 자기 결과에 직접 설정하는 값이라, 도구는 그 값 집합을 작게 유지해야 해요.
호출의 고카디널리티 대상(데이터소스 uid, 없으면 이름)은 절대 메트릭 라벨이 아니에요. mcp.tool.target으로 스팬 전용이고, 엔티티를 지명하지 않는 호출에는 비어 있어요 (아래 참고).
메트릭 이름 발견
| Metric | Type | Description |
|---|---|---|
| mcp_metric_names_response_size_bytes | Native histogram | Bytes consumed from complete successful discovery HTTP responses, after HTTP decompression |
| mcp_metric_names_count | Native histogram | Number of metric names decoded successfully before local filtering and pagination |
이 메트릭들은 prometheus형 백엔드에서 사용 가능한 메트릭 이름을 발견할 때 받는 응답을 이해하는 데 도움을 줘요. 성공한 응답의 크기만 측정해요. 경계(bounded) 백엔드 라벨은 prometheus(호환 백엔드 포함)와 cloud_monitoring을 구분해요. prometheus 백엔드와 달리 Cloud Monitoring은 이름뿐 아니라 더 많은 것을 담는 전체 디스크립터 응답을 측정해서, 두 백엔드의 응답 바이트 크기 비교는 의미가 없어요.
예를 들어 지난 1시간의 p95 응답 크기는:
histogram_quantile(0.95, sum by (backend) (rate(mcp_metric_names_response_size_bytes[1h])))
이 메트릭들은 Native Histogram을 쓰므로 스크래퍼도 네이티브 히스토그램을 지원해야 해요.
Loki 비용 가드레일 메트릭
Loki 쿼리 비용 가드레일을 켰을 때(--loki-guardrail-mode가 shadow 또는 enforce), 가드레일이 평가하는 모든 query_loki_logs 호출은 네 개 카운터 중 정확히 하나만 증가시켜요:
| Metric | Type | Incremented when |
|---|---|---|
| mcp_loki_guardrail_admitted_total | Counter | The query passed every enabled check |
| mcp_loki_guardrail_would_block_total | Counter | The query failed a check in shadow mode and ran anyway |
| mcp_loki_guardrail_blocked_total | Counter | The query failed a check in enforce mode and was rejected |
| mcp_loki_guardrail_fail_open_total | Counter | The guardrail could not reach a verdict and admitted the query |
이 네 개가 가드된 집단을 나누므로, 합이 가드레일이 평가한 호출 수예요. 각각은 점검 횟수가 아니라 쿼리 수예요.
라벨:
| Label | On | Values |
|---|---|---|
| reason | would_block, blocked | selector (no selective label matcher), range (effective time range over the cap), bytes (index/stats estimate over the budget) |
| cause | fail_open | unparseable (no stream selector the scanner recognises), estimate_failed (index/stats unavailable) |
| backend | all four | loki, victorialogs, unknown |
쿼리 하나가 여러 점검을 동시에 걸릴 수 있어요. 가장 먼저 실행된 점검(selector → range → bytes 순)으로 라벨을 붙여 한 번만 세요. 이 순서는 의도적이에요. 선택성 점검은 무조건적이라 selector로 귀속된 쿼리는 --loki-guardrail-max-range나 --loki-guardrail-max-bytes를 올려도 통과되지 않아요. shadow에서 enforce로 승격할 때는 sum(rate(mcp_loki_guardrail_would_block_total[5m]))로 영향받는 집단의 규모를, sum by (reason) (...)로 경계를 조정하면 줄어들지 여부를 읽으세요.
mcp_loki_guardrail_fail_open_total을 함께 보세요. 조용한 would_block 비율은 fail-open 비율도 낮을 때만 "차단될 것이 없다"는 뜻이에요. backend="loki"에서 cause="unparseable" 비율이 높으면 가드레일의 LogQL 스캐너가 사용 중인 쿼리 형태를 인식하지 못한다는 뜻이라서, 경계 조정보다는 스캐너 개선이 필요해요. backend="victorialogs"에서 높은 unparseable 비율은 정상이에요 — 중괄호 없는 LogsQL이 그 곳의 일반적인 형태이고, 바이트 예산 점검은 그 백엔드에서 전혀 실행되지 않아요. 그래서 backend를 따로 접지 않고 라벨로 두는 것이에요.
스트림 셀렉터도 LogQL도 라벨로 내보내지 않아요. 둘 다 무한할 수 있고, 라인 필터가 민감한 리터럴을 담을 수 있거든요. 대신 추출된 셀렉터는 WARN으로, 전체 쿼리는 DEBUG로 로그해요.
OpenTelemetry 추적 활성화하기
OTEL_EXPORTER_OTLP_ENDPOINT(또는 신호별 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)가 설정되면 서버가 OTLP/gRPC로 추적을 내보내요.
로컬 예시:
# Send traces to a local Tempo instance
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
OTEL_EXPORTER_OTLP_INSECURE=true \
./mcp-grafana -t streamable-http
Grafana Cloud 예시:
# Send traces to Grafana Cloud with authentication
OTEL_EXPORTER_OTLP_ENDPOINT=https://tempo-us-central1.grafana.net:443 \
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Basic ..." \
./mcp-grafana -t streamable-http
도구 호출 스팬은 tools/call 같은 이름을 따르고 gen_ai.tool.name, mcp.method.name, mcp.session.id 같은 속성을 포함해요. 서버는 도구 호출 요청의 _meta 필드에서 W3C 트레이스 컨텍스트 전파를 지원해요.
트레이스 컨텍스트 전파
HTTP 전송(SSE 또는 streamable-http)에서는 서버가 양쪽에서 W3C 트레이스 컨텍스트 전파에 참여해서, 호출자와 mcp-grafana, Grafana가 하나로 이어진 트레이스로 보여요:
- 인바운드: 들어오는 요청(MCP 클라이언트나 상위 프록시의)의
traceparent/tracestate헤더가 서버 스팬의 부모가 되어, 새 트레이스를 시작하는 대신 호출자의 트레이스를 이어가요. - 아웃바운드: Grafana API로 가는 요청이
mcp-grafana자신의 스팬을 지명하는traceparent를 담아서, Grafana의 스팬이 우리 스팬에 매달려요.
전파는 항상 활성화돼 있어요. OTEL_EXPORTER_OTLP_ENDPOINT가 필요하지 않아요. 추적 내보내기를 꺼도 스팬은 기록되지 않지만 인바운드 트레이스 컨텍스트는 버려지지 않고 Grafana로 전달돼요.
사용되는 전파자는 표준 OTEL_PROPAGATORS 환경 변수로 구성되며 기본은 tracecontext,baggage예요. 비-W3C 시스템(b3, b3multi, jaeger, xray, ottrace, 쉼표로 구분한 조합)과 상호운용하려면 설정하고, 전파를 완전히 끄려면 none으로 설정해요.
도구 호출 요청은 MCP _meta 필드에 traceparent/tracestate를 담을 수도 있어요. 있으면 그 컨텍스트가 도구 스팬의 부모가 돼요. 이것은 HTTP 요청에서 헤더를 읽을 수 없는 stdio에서 유일한 전파 채널이에요.
GRAFANA_FORWARD_HEADERS에 traceparent나 tracestate를 나열할 필요도 없고 더 이상 효과도 없어요. 호출자의 traceparent를 그대로 전달하면 Grafana의 스팬이 호출자에 매달리고 mcp-grafana가 트레이스 중간에서 빠지게 되므로, 전달된 값이 전파된 값을 절대 덮어쓰지 않아요. OTEL_PROPAGATORS=none으로 전파를 끄면 전달된 추적 헤더는 여전히 적용돼요.
도구 호출 스팬은 도구 호출 차원 mcp.tool.operation, mcp.tool.resource_type, mcp.tool.phase와, 한 엔티티를 건드리는 스팬을 그룹화하기 위한 스팬 전용 mcp.tool.target(데이터소스 uid, 없으면 이름)도 담아요. mcp.tool.target은 호출이 엔티티를 지명하지 않을 때 — 특히 데이터소스가 아직 존재하지 않는 create_datasource의 phase=schema 호출 — 비어 있어서, 그 호출을 나중의 phase=created 호출에 이어 붙이는 것은 공유 트레이스(클라이언트가 _meta로 컨텍스트를 전파할 때)나 mcp.session.id + mcp_tool_resource_type에 의존해요. 메트릭 라벨과 달리 이것들은 모든 도구에 원시 값으로 붙어요 (허용 목록도, other 버킷도 없음) — 트레이스는 설계상 고카디널리티니까요. HTTP 전송(SSE 또는 streamable-http — 풍부하게 만들 서버 스팬이 존재하려면)과 추적 활성화가 필요하지만 --metrics는 필요 없어요. stdio이거나 추적이 꺼져 있으면 보강은 no-op이에요.
OpenTelemetry 로그 활성화하기
OTEL_EXPORTER_OTLP_ENDPOINT(또는 신호별 OTEL_EXPORTER_OTLP_LOGS_ENDPOINT)가 설정되면 서버는 기존의 평문 stderr 출력에 더해 구조화 로그도 OTLP/gRPC로 내보내요. 로그는 활성 스팬의 trace_id와 span_id를 담아서 내보낸 추적과 상관관계를 이뤄요.
추적과 로그는 엔드포인트를 독립적으로 해석하므로 두 신호를 따로 켤 수 있어요:
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT만 설정하면 추적만 활성화되고 로그 내보내기는 없어요.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT만 설정하면 로그 내보내기만 되고 추적은 없어요.- 일반
OTEL_EXPORTER_OTLP_ENDPOINT를 설정하면 둘 다 활성화돼요.
# Send logs and traces to a local OTel collector
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 \
OTEL_EXPORTER_OTLP_INSECURE=true \
./mcp-grafana -t streamable-http
stderr 로깅은 그대로 계속돼요. 운영자는 로그만 OTel 컬렉터로 보내고 싶으면 stderr를 /dev/null로 파이프하면 돼요.
로그는 OTEL_EXPORTER_OTLP_LOGS_ENDPOINT(또는 일반 OTEL_EXPORTER_OTLP_ENDPOINT)를 원격 gRPC 엔드포인트에 지정하고 OTEL_EXPORTER_OTLP_LOGS_HEADERS(또는 OTEL_EXPORTER_OTLP_HEADERS)로 인증을 제공하면 Grafana Cloud처럼 OTLP/gRPC를 받는 어떤 관리형 백엔드로든 직접 보낼 수 있어요. 위 추적 예시와 같은 방식이죠. 로컬 OTel 컬렉터는 선택 사항이에요 — fan-out, 배칭, 다중 백엔드 라우팅에 유용하지만 필수는 아니에요.
신호별 변형 OTEL_EXPORTER_OTLP_LOGS_ENDPOINT, OTEL_EXPORTER_OTLP_LOGS_HEADERS, OTEL_EXPORTER_OTLP_LOGS_INSECURE, OTEL_EXPORTER_OTLP_LOGS_CERTIFICATE, OTEL_EXPORTER_OTLP_LOGS_TIMEOUT, OTEL_EXPORTER_OTLP_LOGS_COMPRESSION은 반영되어 일반 OTEL_EXPORTER_OTLP_* 대응 항목을 덮어써요 — 전체 목록과 우선순위 규칙은 OTel exporter 사양을 참고하세요.
참고: 구성된 컬렉터에 도달할 수 없으면 로그 레코드는 메모리에 버퍼링되고(기본 큐: 2048) 큐가 차면 가장 오래된 레코드가 버려져요. 프로세스는 서비스를 막지 않고 계속돼요. 장애 중 손실 없는 버퍼링이 필요하면 로컬 OTel 컬렉터를 구성하세요.
로그는 stdio 전송에서도 내보내져서, IDE 클라이언트가 불러낸 로컬 mcp-grafana 인스턴스의 로그를 중앙화하기 쉬워요.
Docker로 실행하기 (메트릭, 추적, 로그)
docker run --rm -p 8000:8000 \
-e GRAFANA_URL=http://localhost:3000 \
-e GRAFANA_SERVICE_ACCOUNT_TOKEN= \
-e OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 \
-e OTEL_EXPORTER_OTLP_INSECURE=true \
grafana/mcp-grafana \
-t streamable-http --metrics