MCP 서버 옵저버빌리티 완전 가이드: Prometheus 메트릭·분산 추적·이상 탐지까지
복잡한 에이전트 워크플로 하나가 단일 사용자 세션에서 수십~수백 건의 Tool Call을 발생시킵니다. 각각의 호출은 레이턴시를 소모하고, 에러를 만들어내며, 보안 이벤트를 남깁니다. 이 흐름이 블랙박스로 남아 있다면, 레이턴시 급증이나 비정상적인 호출 패턴을 사전에 감지하지 못해 시스템 장애나 보안 사고로 이어질 수 있습니다. 문제는 MCP가 JSON-RPC 2.0 기반의 비교적 새로운 프로토콜이라, 기존 APM 도구만으로는 충분한 가시성을 확보하기 어렵다는 점입니다.
이 글에서는 OpenTelemetry MCP 시맨틱 컨벤션, Prometheus 메트릭 계측, 분산 추적 컨텍스트 전파, SIEM 이상 탐지까지 네 단계로 구성된 MCP 옵저버빌리티 파이프라인을 단계별로 살펴봅니다. 이 글을 읽고 나면, MCP 서버에 구조화 로그·메트릭·분산 추적을 통합 적용하고, 비정상 Tool Call 패턴을 실시간으로 탐지하는 파이프라인을 직접 구성할 수 있습니다.
시작하기 전에 사전 요구사항을 안내합니다. Python 또는 TypeScript 기초 문법과 Docker 기본 사용 경험이 있다면 대부분의 예시를 따라올 수 있습니다. OTel Collector YAML이나 PromQL은 각 섹션에서 하나씩 설명하므로 처음 접하더라도 무리 없이 읽을 수 있습니다. 실제 타깃 독자는 MCP를 프로덕션에 배포 중이거나 배포를 검토 중인 백엔드 또는 MLOps 엔지니어입니다.
핵심 개념
MCP와 옵저버빌리티 3대 요소
MCP(Model Context Protocol)는 Anthropic이 설계하고 2024년 오픈소스로 공개한 AI 에이전트-툴 통신 표준 프로토콜입니다. JSON-RPC 2.0 위에서 동작하며, AI 에이전트(Client)가 외부 도구나 데이터 소스를 제공하는 MCP 서버(Server)와 표준화된 방식으로 통신합니다.
📌 Tool Call이란? 에이전트가 MCP 서버에 요청하는 단위 작업입니다. 내부적으로
tools/call메시지 타입으로 JSON-RPC 요청이 직렬화되어 전송됩니다.
MCP 환경에서 옵저버빌리티는 세 가지 축으로 구성됩니다.
| 요소 | 역할 | 대표 도구 |
|---|---|---|
| 구조화 로그 (Logs) | Tool Call 요청/응답, 에러 원인 기록 | Loki, Datadog Logs |
| 메트릭 (Metrics) | 레이턴시·호출 수·에러율 시계열 집계 | Prometheus, Grafana Mimir |
| 분산 추적 (Traces) | 에이전트 추론 → Tool 실행 흐름 연결 | Jaeger, Grafana Tempo |
세 요소가 통합된 전체 파이프라인 구조는 아래와 같습니다.
MCP Client (에이전트)
│ tools/call + _meta.traceparent (W3C Trace Context)
▼
MCP Server (Python/FastAPI)
│ OTLP gRPC (트레이스·메트릭·로그) │ 구조화 JSON 로그
▼ ▼
OTel Collector Datadog Cloud SIEM
│ (이상 탐지 룰)
├──▶ Prometheus (메트릭)
│ │
│ └──▶ Grafana (대시보드·알람)
│
├──▶ Grafana Tempo (분산 추적)
│
└──▶ Loki (로그)OpenTelemetry MCP 시맨틱 컨벤션
OpenTelemetry는 gen-ai/mcp/ 네임스페이스 아래 MCP 전용 Span 속성과 메트릭을 시맨틱 컨벤션으로 정의했습니다. 기존 RPC 컨벤션 대신 MCP 전용 컨벤션 사용이 권장됩니다.
⚠️ 안정성 주의: 현재 Gen AI 관련 OTel 시맨틱 컨벤션의 대부분은
experimental상태입니다. 프로덕션 도입 전에 사용 중인 OTel SDK 버전을 고정하고, 컨벤션 변경 사항을 릴리스 노트에서 정기적으로 확인하는 것을 권장합니다.
Span 이름은 다음 형식을 따릅니다.
{mcp.method.name} {target}
예: tools/call weather_tool주요 Span 속성 예시입니다.
| 속성 키 | 의미 | 예시 값 |
|---|---|---|
mcp.method.name |
호출된 MCP 메서드 | tools/call |
mcp.tool.name |
실행된 Tool 이름 | get_weather |
mcp.session.id |
세션 식별자 (트레이스 속성에만 사용) | sess_abc123 |
gen_ai.usage.input_tokens |
입력 토큰 수 | 412 |
error.type |
에러 분류 | timeout |
분산 추적 컨텍스트 전파
MCP가 JSON-RPC 2.0 기반이라는 점은 한 가지 중요한 제약을 만듭니다.
📌 JSON-RPC 2.0 간략 설명: JSON-RPC 2.0에서
params객체는 메서드 인자를 담는 표준 필드입니다._meta는 프로토콜 확장을 위한 비표준 네임스페이스로, MCP 명세에서 애플리케이션이 자유롭게 사용할 수 있도록 예약되어 있습니다.
HTTP 헤더를 통한 W3C Trace Context 전파를 네이티브로 지원하지 않기 때문에, params._meta 프로퍼티 백에 traceparent와 tracestate를 주입하는 방식을 사용합니다.
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "Seoul" },
"_meta": {
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01",
"tracestate": "vendor=myapp"
}
},
"id": 1
}서버 측에서는 이 값을 파싱해 OTel 컨텍스트로 복원합니다.
from opentelemetry.propagate import extract
from opentelemetry.trace import get_tracer
from typing import Any, TypedDict
tracer = get_tracer("mcp-server")
class McpParams(TypedDict, total=False):
name: str
arguments: dict[str, Any]
_meta: dict[str, str]
class McpRequest(TypedDict):
jsonrpc: str
method: str
params: McpParams
id: int | str
def execute_tool(params: McpParams) -> Any:
"""실제 Tool 실행 로직으로 대체하세요."""
raise NotImplementedError(f"Tool '{params.get('name')}' not implemented")
def handle_tool_call(request: McpRequest) -> Any:
"""_meta에서 W3C Trace Context를 추출해 OTel 스팬과 연결합니다."""
meta = request.get("params", {}).get("_meta", {})
carrier = {
"traceparent": meta.get("traceparent", ""),
"tracestate": meta.get("tracestate", ""),
}
ctx = extract(carrier)
tool_name = request["params"].get("name", "unknown")
with tracer.start_as_current_span(
f"tools/call {tool_name}",
context=ctx,
attributes={
"mcp.method.name": "tools/call",
"mcp.tool.name": tool_name,
},
):
return execute_tool(request["params"])실전 적용
아래 네 단계는 순서대로 진행할 수도 있고, 각각 독립적으로 적용할 수도 있습니다. Python MCP 서버(Step 1, 2, 4)와 TypeScript 에이전트(Step 3)는 실제 아키텍처에서 자주 볼 수 있는 조합입니다. Python은 ML/AI 라이브러리와 결합한 서버 구현에, TypeScript는 에이전트 오케스트레이션 레이어에 주로 사용되기 때문입니다. Python만 사용하신다면 Step 3의 TypeScript 로직을 Python OTel SDK로 동일하게 구현할 수 있습니다.
Step 1: Python MCP 서버에 Prometheus 메트릭 계측하기
FastAPI 기반 MCP 서버에 prometheus-fastapi-instrumentator를 결합해 Tool별 레이턴시와 에러율을 수집하는 예시입니다.
from fastapi import FastAPI
from prometheus_fastapi_instrumentator import Instrumentator
from prometheus_client import Histogram, Counter
from typing import Any, TypedDict
import time
app = FastAPI()
class ToolCallRequest(TypedDict):
params: dict[str, Any]
# MCP 전용 커스텀 메트릭 정의
# 주의: 낮은 카디널리티 레이블(tool_name, status)만 사용합니다.
# user_id처럼 고유 값이 많은 필드는 Prometheus 레이블이 아닌
# 트레이스 속성이나 로그 필드로 기록하세요.
tool_duration = Histogram(
"mcp_tool_invocation_duration_seconds",
"MCP Tool Call 실행 시간",
labelnames=["tool_name", "status"],
buckets=[0.01, 0.05, 0.1, 0.5, 1.0, 5.0],
)
tool_errors = Counter(
"mcp_tool_errors_total",
"MCP Tool Call 에러 총 횟수",
labelnames=["tool_name", "error_type"],
)
tool_calls = Counter(
"mcp_tool_calls_total",
"MCP Tool Call 총 호출 수",
labelnames=["tool_name"],
)
# FastAPI 기본 메트릭 자동 계측
Instrumentator().instrument(app).expose(app)
async def execute_tool(tool_name: str, arguments: dict[str, Any]) -> Any:
"""실제 Tool 실행 로직으로 대체하세요."""
raise NotImplementedError(f"Tool '{tool_name}' not implemented")
@app.post("/mcp/tools/call")
async def call_tool(request: ToolCallRequest) -> Any:
tool_name = request["params"].get("name", "unknown")
tool_calls.labels(tool_name=tool_name).inc()
start = time.time()
try:
result = await execute_tool(
tool_name, request["params"].get("arguments", {})
)
duration = time.time() - start
tool_duration.labels(tool_name=tool_name, status="success").observe(duration)
return result
except Exception as e:
duration = time.time() - start
tool_duration.labels(tool_name=tool_name, status="error").observe(duration)
tool_errors.labels(tool_name=tool_name, error_type=type(e).__name__).inc()
raise/metrics 엔드포인트에서 노출되는 메트릭을 Prometheus가 스크랩하면 Grafana에서 아래와 같은 PromQL로 레이턴시를 조회할 수 있습니다.
# Tool별 p95 레이턴시
histogram_quantile(
0.95,
rate(mcp_tool_invocation_duration_seconds_bucket[5m])
) by (tool_name)
# 분당 에러율
rate(mcp_tool_errors_total[1m]) by (tool_name, error_type)
# Tool별 호출량 추이
rate(mcp_tool_calls_total[10m]) by (tool_name)💡 카디널리티(Cardinality)란? Prometheus에서 레이블 조합의 고유한 수를 의미합니다.
session_id처럼 매번 달라지는 값이나 수천 명 이상의user_id를 레이블로 사용하면 시계열 수가 폭발적으로 증가해 메모리 문제가 발생합니다. 사용자별 분석이 필요하다면user_id는 트레이스 속성이나 로그 필드로 기록하고, Prometheus 레이블은tool_name,status처럼 값의 종류가 제한된 것만 사용하는 것을 권장합니다.
Step 2: OpenTelemetry Collector로 통합 파이프라인 구성하기
OTel Collector를 중앙 허브로 두면 MCP 서버에서 발생하는 메트릭·트레이스·로그를 단일 파이프라인으로 수집해 여러 백엔드로 라우팅할 수 있습니다.
# otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
# MCP 서버 Prometheus /metrics 스크랩
prometheus:
config:
scrape_configs:
- job_name: "mcp-server"
scrape_interval: 15s
static_configs:
- targets: ["mcp-server:8000"]
processors:
# PII 마스킹 — Tool 인자에서 민감 정보 제거
# replace_pattern()은 OTTL(OpenTelemetry Transformation Language) 문법입니다.
# 아래 정규식의 YAML 이스케이프 처리는 실제 환경에서 반드시 검증하세요.
transform/mask_pii:
error_mode: ignore
trace_statements:
- context: span
statements:
- replace_pattern(attributes["mcp.tool.arguments"],
"\"password\"\\s*:\\s*\"[^\"]+\"",
"\"password\": \"[REDACTED]\"")
# 배치 처리로 오버헤드 감소
batch:
send_batch_size: 1000
timeout: 10s
# 메모리 제한
memory_limiter:
check_interval: 1s
limit_mib: 512
exporters:
# Grafana Tempo로 트레이스 전송
otlp/tempo:
endpoint: tempo:4317
tls:
insecure: true
# Prometheus Remote Write로 메트릭 전송
prometheusremotewrite:
endpoint: "http://prometheus:9090/api/v1/write"
# Loki로 로그 전송
loki:
endpoint: http://loki:3100/loki/api/v1/push
service:
pipelines:
traces:
receivers: [otlp]
processors: [transform/mask_pii, batch]
exporters: [otlp/tempo]
metrics:
receivers: [otlp, prometheus]
processors: [batch, memory_limiter]
exporters: [prometheusremotewrite]
logs:
receivers: [otlp]
processors: [transform/mask_pii, batch]
exporters: [loki]Step 3: 에이전트 추론과 Tool Call을 End-to-End 트레이스로 연결하기
에이전트 추론 스팬에서 MCP Tool Call 스팬으로 이어지는 부모-자식 트레이스 구조를 TypeScript로 구성하는 예시입니다.
📌 Node.js 버전 참고: 아래 예시는
crypto.randomUUID()를 사용합니다. Node.js 15 이상에서 지원됩니다. 14 이하 환경에서는uuid패키지를 설치한 뒤import { v4 as uuidv4 } from 'uuid'로 대체하세요.
import { trace, context, SpanStatusCode } from "@opentelemetry/api";
import { W3CTraceContextPropagator } from "@opentelemetry/core";
const tracer = trace.getTracer("mcp-agent", "1.0.0");
const propagator = new W3CTraceContextPropagator();
interface ToolPlan {
name: string;
sessionId: string;
args: Record<string, unknown>;
}
// Step 1에서 구성한 MCP 서버의 /mcp/tools/call 엔드포인트를 호출합니다.
async function callMcpServer(request: unknown): Promise<unknown> {
// 예: fetch("http://mcp-server:8000/mcp/tools/call", { body: JSON.stringify(request) })
throw new Error("callMcpServer: 실제 HTTP 요청 로직으로 대체하세요");
}
async function planToolCalls(userQuery: string): Promise<ToolPlan[]> {
// 실제 LLM 추론 로직으로 대체하세요
return [];
}
async function runAgentWithObservability(userQuery: string): Promise<void> {
// 에이전트 추론 스팬 시작 (루트)
return await tracer.startActiveSpan(
"gen_ai.agent reasoning",
{
attributes: {
"gen_ai.system": "anthropic",
"gen_ai.request.model": "claude-sonnet-4-6",
"user.query": userQuery,
},
},
async (agentSpan) => {
try {
const toolsToCall = await planToolCalls(userQuery);
for (const tool of toolsToCall) {
// Tool Call 스팬을 에이전트 스팬의 자식으로 생성
await tracer.startActiveSpan(
`tools/call ${tool.name}`,
{
attributes: {
"mcp.method.name": "tools/call",
"mcp.tool.name": tool.name,
"mcp.session.id": tool.sessionId,
},
},
async (toolSpan) => {
try {
// _meta에 traceparent 주입 (W3C Trace Context 전파)
const carrier: Record<string, string> = {};
propagator.inject(context.active(), carrier);
const mcpRequest = {
jsonrpc: "2.0",
method: "tools/call",
params: {
name: tool.name,
arguments: tool.args,
_meta: {
traceparent: carrier["traceparent"] ?? "",
tracestate: carrier["tracestate"] ?? "",
},
},
id: crypto.randomUUID(), // Node.js 15+ 필요
};
const result = await callMcpServer(mcpRequest);
toolSpan.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (err) {
toolSpan.setStatus({
code: SpanStatusCode.ERROR,
message: String(err),
});
toolSpan.recordException(err as Error);
throw err;
} finally {
toolSpan.end();
}
}
);
}
agentSpan.setStatus({ code: SpanStatusCode.OK });
} finally {
agentSpan.end();
}
}
);
}이 구조로 생성된 트레이스는 Grafana Tempo나 Jaeger에서 아래와 같은 계층 구조로 시각화됩니다.
gen_ai.agent reasoning (350ms)
├── tools/call get_weather (45ms)
├── tools/call query_database (180ms)
│ └── db.query SELECT ... (120ms)
└── tools/call send_report (80ms)Step 4: Datadog Cloud SIEM으로 Tool Call 이상 탐지하기
MCP 서버의 구조화 로그를 Datadog에 수집하고 이상 탐지 룰을 적용하는 예시입니다.
import logging
import json
from datetime import datetime, timezone
from typing import Optional
logger = logging.getLogger("mcp.security")
def log_tool_call(
tool_name: str,
user_id: str,
session_id: str,
status: str,
duration_ms: float,
error: Optional[str] = None,
) -> None:
"""Datadog SIEM이 파싱할 수 있는 구조화 로그를 출력합니다."""
log_entry: dict = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"event.category": "mcp.tool_call",
"mcp.tool.name": tool_name,
"usr.id": user_id, # Datadog 표준 사용자 속성
"mcp.session.id": session_id,
"http.status": 200 if status == "success" else 500,
"duration_ms": duration_ms,
"status": status,
}
if error:
log_entry["error.message"] = error
log_entry["error.kind"] = "MCPToolError"
logger.info(json.dumps(log_entry))아래는 Datadog Cloud SIEM에서 설정할 수 있는 탐지 룰의 **개념 설명용 의사 구문(pseudocode)**입니다. 실제 Datadog Detection Rule 쿼리 언어와 일부 문법이 다를 수 있으므로, Datadog 공식 문서를 참조해 각 환경에 맞게 조정하는 것을 권장합니다.
# 탐지 룰 1: 특정 Tool 호출 빈도 급증
# 사용자별 Tool Call이 기준 편차 3σ를 초과할 때 경보
@event.category:mcp.tool_call
| anomaly(count, direction:above, threshold:3)
by usr.id, mcp.tool.name
over 5m
# 탐지 룰 2: 연속 인증 실패 후 Tool 호출 시도
# 같은 세션에서 401 이후 5분 내 tools/call 탐지
@event.category:mcp.tool_call AND @http.status:401
| sequence by mcp.session.id
[within 5m] @http.status:401
[within 5m] @event.category:mcp.tool_call
# 탐지 룰 3: 비정상 시간대 고빈도 호출 (야간 자동화 탐지)
@event.category:mcp.tool_call
| @timestamp:[23:00 TO 06:00]
| count > 100 by usr.id over 10m장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 표준화된 계측 | OTel 시맨틱 컨벤션이 MCP를 지원해 벤더 락인 없이 계측 가능 |
| 기존 인프라 재활용 | Prometheus·Grafana·Datadog 등 기존 모니터링 스택을 그대로 연결 가능 |
| 풍부한 컨텍스트 | Tool 이름·인자·토큰 사용량·레이턴시를 하나의 스팬에 기록 |
| 보안과 옵저버빌리티 통합 | SIEM 이상 탐지와 성능 모니터링을 동일 파이프라인에서 처리 |
| 에이전트 워크플로 가시성 | 멀티 스텝 에이전트 실행을 End-to-End 트레이스로 추적 가능 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 컨텍스트 전파 비표준 | JSON-RPC 기반 MCP는 W3C Trace Context를 네이티브 지원하지 않음 | params._meta에 traceparent 주입하는 방식으로 구현 |
| 인자 민감도 문제 | Tool 인자에 PII·시크릿이 포함될 수 있음 | OTel Collector의 transform processor로 마스킹 정책 적용 |
| 카디널리티 폭발 | 고유 값이 많은 필드를 Prometheus 레이블로 사용하면 시계열 수 급증 | 레이블은 tool_name, status처럼 값의 종류가 제한된 것만 사용 |
| 에코시스템 미성숙 | OTel MCP 시맨틱 컨벤션이 experimental 상태이며 자동 계측 라이브러리 부족 |
SDK 버전 고정, OpenLIT·opentelemetry-instrumentation-mcp 등 신생 라이브러리 정기 모니터링 |
| 레이턴시 오버헤드 | Tool Call마다 스팬 생성 시 고빈도 워크로드에서 10~30ms 오버헤드 발생 가능 | 배치 프로세서 설정, 샘플링 비율 조정으로 완화 |
| 비용 귀속 | 사용자·팀별 비용 귀속을 위한 토큰 추적이 별도 설계 필요 | gen_ai.usage.input_tokens 속성을 Prometheus Counter로 집계 |
실무에서 가장 흔한 실수
- Tool 인자를 그대로 로그·스팬에 기록하기: 비밀번호, API 키, 개인정보가 텔레메트리 데이터에 노출됩니다. OTel Collector의 transform processor에서 민감 필드를 마스킹하는 규칙을 설정하는 것을 권장합니다.
- 고카디널리티 값을 Prometheus 레이블로 사용하기: 세션 ID나 수천 명 이상의 사용자 ID는 Prometheus 레이블로 적합하지 않습니다. 이러한 식별자는 트레이스 속성이나 로그 필드에 기록하고, Prometheus 레이블은
tool_name,status처럼 값의 종류가 제한된 것만 사용하는 것이 바람직합니다. - 에이전트 루트 스팬 없이 Tool Call 스팬만 생성하기: Tool Call 스팬이 고립되면 어떤 사용자 요청에서 어떤 순서로 도구가 호출됐는지 추적이 불가능합니다. 에이전트 추론을 루트 스팬으로 먼저 열고, 이하 Tool Call을 자식 스팬으로 연결하는 구조가 효과적입니다.
마치며
MCP 옵저버빌리티는 단순한 로그 수집을 넘어, 에이전트 추론부터 Tool 실행까지 전체 흐름을 하나의 트레이스로 연결하고, 비정상 패턴을 실시간으로 탐지하는 통합 파이프라인입니다. 네 단계를 모두 갖추면 레이턴시 급증·에러 폭발·이상 호출 패턴 어느 하나도 블랙박스로 남지 않게 됩니다.
지금 바로 시작해볼 수 있는 3단계입니다. 각 단계는 독립적으로 적용할 수도 있고, 순서대로 쌓아올릴 수도 있습니다.
- Prometheus 메트릭 추가:
pip install prometheus-fastapi-instrumentator로/metrics엔드포인트를 노출하고,mcp_tool_invocation_duration_secondsHistogram을 Tool Call 핸들러에 추가합니다. Prometheus·Grafana가 이미 구성된 환경이라면 연동까지 한 번에 완료할 수 있습니다. - OTel Collector 배포 및 트레이스 파이프라인 구성: Step 2의
otel-collector-config.yaml을 시작점으로 Grafana Tempo나 Jaeger를 트레이스 백엔드로 연결하고,params._meta를 통한 컨텍스트 전파를 적용합니다. 이 단계에서 에이전트 추론-Tool 실행 전체 흐름이 하나의 트레이스로 연결됩니다. - 이상 탐지 룰 적용: Datadog을 사용하신다면 Cloud SIEM에 Step 4의 탐지 개념을 참고해 룰을 구성할 수 있습니다. 오픈소스 스택이라면 Grafana Alert과 Prometheus 이상 탐지 조합으로 비정상 Tool Call 빈도 경보를 구성할 수 있습니다.
옵저버빌리티 파이프라인이 갖춰지면 다음 과제는 MCP 요청 자체를 게이트웨이 레이어에서 제어하는 것입니다. 다음 글에서는 Kong AI Gateway 3.12를 MCP 프록시로 배치해 OAuth 2.1 인증, Rate Limiting, 팀별 비용 귀속을 단일 레이어에서 처리하는 방법을 살펴볼 예정입니다.
다음 글: MCP 서버에 Kong AI Gateway 3.12를 프록시로 배치해 OAuth 2.1 인증, Rate Limiting, 팀별 비용 귀속을 단일 레이어에서 처리하는 방법을 다룰 예정입니다.
참고 자료
공식 문서
- Semantic conventions for Model Context Protocol (MCP) | OpenTelemetry
- Prometheus and OpenTelemetry - Better Together | OpenTelemetry
엔지니어링 블로그
- MCP Observability with OpenTelemetry | SigNoz
- Monitor MCP servers with OpenLIT and Grafana Cloud | Grafana Labs
- MCP security risks: How to build SIEM detection rules | Datadog
- Securing, Observing, and Governing MCP Servers with Kong AI Gateway | Kong
- Distributed tracing for agentic workflows with OpenTelemetry | Red Hat Developer
- How to Instrument MCP Servers with OpenTelemetry for Production Observability | OneUptime
- Prometheus MCP Server: AI-Driven Monitoring Intelligence for AWS Users | AWS Blog
커뮤니티 가이드