OpenTelemetry에서 MCP 도구 호출당 Amazon Bedrock 토큰 비용 추적하기
요약
OpenTelemetry를 사용하여 MCP 도구 호출 시 발생하는 Amazon Bedrock의 토큰 사용량과 비용을 추적하는 방법을 설명합니다. opentel-mcp v0.5.0 업데이트를 통해 트레이스 데이터에 비용 및 토큰 정보를 포함하여 정밀한 비용 분석이 가능해졌습니다.
핵심 포인트
- 표준 MCP 인스트루멘테이션은 지연 시간과 에러는 기록하지만 비용 정보는 놓침
- opentel-mcp v0.5.0에서 토큰 사용량 및 USD 비용 속성 추가
- OpenTelemetry GenAI 시맨틱 컨벤션을 준수하여 기존 대시보드와 호환 가능
- 어떤 도구와 워크플로가 비용을 주도하는지 정밀하게 추적 가능
이미 MCP 트레이스(traces)를 통해 무엇이 느렸고 무엇이 고장 났는지는 알 수 있습니다. 여기 Amazon Bedrock 청구서가 도착하기 전에, 트레이스가 비용이 얼마나 들었는지 알려주게 만드는 방법을 소개합니다.
3주 늦게 도착하는 청구서
지난달 저는 몇 가지 Amazon Bedrock 호출을 처리하는 MCP 서버의 부하 테스트(load-testing)를 진행하고 있었습니다. 지연 시간(latency)은 괜찮아 보였고, 에러율(error rate)도 괜찮았습니다. 모든 스팬(span)은 초록색이었습니다.
그러다 Cost Explorer 차트가 나왔고, 하나의 도구가 모델 비용 지출의 대부분을 차지하고 있다는 사실을 알게 되었습니다.
어떤 도구인지 확인하기 위해 트레이스로 돌아갔습니다. 하지만 트레이스는 전혀 알지 못했습니다. 트레이스에는 지속 시간(duration), 상태 코드(status code), 도구 이름(tool name)만 있었을 뿐, 토큰(tokens)이나 달러($)에 대한 정보는 없었습니다. 제가 필요로 했던 정보는 이미 3주 전에 버려진 상태였습니다.
이 포스트는 바로 그 간극에 관한 것이며, 이를 메우기 위해 opentel-mcp v0.5.0에 반영한 내용입니다.
표준 MCP 인스트루멘테이션(instrumentation)이 비용을 완전히 놓치는 이유
전형적인 MCP 서버는 다음과 같은 체인의 중간에 위치합니다:
사용자(User) → MCP 클라이언트(MCP Client) → MCP 서버(MCP Server) → 도구(Tool) → Amazon Bedrock / Anthropic / OpenAI / Gemini
표준 OpenTelemetry 인스트루멘테이션은 MCP 서버 계층을 감싸고 다음을 기록합니다:
요청 지연 시간 (request latency)
에러 상태 (error status)
성공률 (success rate)
모두 유용한 정보입니다. 하지만 모델 호출은 도구 내부, 즉 한 단계 아래 계층에서 발생하며, 사용량 페이로드(usage payload)는 도구가 응답을 생성할 때 소비되고 버려집니다. 스팬이 닫힐 때쯤이면 토큰 수(token counts)는 사라진 상태가 됩니다.
따라서 새벽 2시에 실제로 답을 얻고 싶은 질문들은 여전히 답을 얻지 못한 채 남게 됩니다:
어떤 도구가 가장 많은 토큰을 소모하고 있는가?
어떤 워크플로(workflow)가 호출당 비용이 가장 많이 드는가?
어떤 모델이 지출을 주도하고 있는가?
어떤 사용자 세션이 예산을 초과했는가?
이러한 답변은 제공업체의 빌링 대시보드(billing dashboard)에만 존재하며, 트레이스 ID(trace ID)가 첨부되지 않은 채 일 단위로 집계되어 있습니다. 돈을 썼다는 것은 볼 수 있지만, 어떤 요청이 그 비용을 발생시켰는지는 볼 수 없습니다.
스팬에서의 비용 할당(cost attribution) 모습
v0.5.0에서는 모든 MCP 도구 스팬(tool span)이 지연 시간 및 에러와 함께 비용 및 토큰 데이터를 일급 속성(first-class attributes)으로 포함합니다.
토큰 사용량 (Token usage)
mcp.tool.tokens.input mcp.tool.tokens.output mcp.tool.tokens.total
비용 (Cost)
`mcp.tool.cost.usd
모델 속성 (Model attribution)
mcp.tool.model
gen_ai.response.model
두 번째 항목은 보기보다 더 중요합니다.gen_ai.response.model`은 OpenTelemetry GenAI 시맨틱 컨벤션 (semantic conventions)의 일부이므로, 이미 Grafana, SigNoz 또는 Amazon Managed Grafana에서 GenAI 대시보드를 구축해 두었다면 단 하나의 패널도 수정하지 않고 MCP 도구 스팬 (span)이 대시보드에 나타납니다. 두 가지를 모두 내보내는 것은 약간의 중복이지만, 모든 다운스트림 (downstream) 사용자가 대시보드를 다시 구축해야 하는 수고를 덜어줍니다.
예산 신호 (Budget signals)
mcp.tool.cost.budget_exceeded mcp.tool.cost.budget_scope
두 개의 새로운 메트릭 카운터 (metrics counters)
mcp.tool.tokens.total mcp.tool.cost.total
카운터는 알림 (alerting) 및 추세선 (trend lines)을 위한 집계된 뷰 (aggregate view)를 제공합니다. 스팬 속성 (span attributes)은 알림이 발생했을 때 상세 분석 (drill-down)을 가능하게 합니다. 두 가지 모두가 필요합니다. 카운터는 지출이 3배로 늘어났음을 알려주고, 스팬은 어떤 도구가 그 원인인지 알려줍니다.
Bedrock 기반 MCP 서버 계측 (Instrumenting)
설정은 한 줄이면 충분합니다. 이미 OpenTelemetry Node SDK가 구성되어 있다면, 변경 사항은 다음과 같습니다:
javascript
`import { instrumentMcpServer } from "opentel-mcp";
instrumentMcpServer(server);`
이 시점부터 모든 도구 호출은 트레이스 컨텍스트 (trace context), 토큰 사용량 (token usage), 예상 비용 (estimated cost), 모델 속성 (model attribution), 그리고 예산 신호 (budget signals)를 내보냅니다.
다음은 Bedrock 도구가 엔드 투 엔드 (end to end)로 계측되는 과정입니다:
javascript
`import { BedrockRuntimeClient, InvokeModelCommand } from "@aws-sdk/client-bedrock-runtime";
import { instrumentMcpServer } from "opentel-mcp";
const bedrock = new BedrockRuntimeClient({ region: "us-east-1" });
server.tool("summarize_report", async ({ text }) => {
const response = await bedrock.send(new InvokeModelCommand({
modelId: "amazon.nova-pro-v1:0",
body: JSON.stringify({
messages: [{ role: "user", content: [{ text }] }]
})
}));
const result = JSON.parse(new TextDecoder().decode(response.body));
return {
content: [{ type: "text", text: result.output.message.content[0].text }],
// usage flows through to the span
_meta: { usage: result.usage, model: "amazon.nova-pro-v1:0" }
};
});
instrumentMcpServer(server);
`
The resulting span:
Span: mcp.tool.summarize_report
├─ duration: 1,842ms
├─ mcp.tool.model: amazon.nova-pro-v1:0
├─ mcp.tool.tokens.input: 4,210
├─ mcp.tool.tokens.output: 388
├─ mcp.tool.tokens.total: 4,598
└─ mcp.tool.cost.usd: 0.00363
One tool call. One line in the trace. Latency, model, tokens, and dollars in the same place — which means you can now sort your traces by cost the same way you sort them by duration.
Which models are priced out of the box
v0.5.0 ships pricing for 19 models across five providers:
Provider Coverage
Amazon Bedrock Nova family
Anthropic Claude family
OpenAI GPT family
Google Gemini family
DeepSeek DeepSeek models
If you're on a supported model, there's no pricing configuration to write.
If you're running a fine-tuned model, a self-hosted model, or you have negotiated enterprise rates, extend the defaults:
javascript
`import { instrumentMcpServer, DEFAULT_PRICING } from "opentel-mcp";
instrumentMcpServer(server, {
pricing: {
...DEFAULT_PRICING,
"internal-model": {
input: 0.002, // USD per 1K input tokens
output: 0.008 // USD per 1K output tokens
}
},
budgets: {
toolUsd: 0.10,
sessionUsd: 1.00
}
});`
Every provider returns usage in a different shape, so the default usage extractor is fully replaceable if your tool results don't match the common formats.
Four design decisions worth explaining
It observes. It does not enforce.
When budgets.toolUsd is exceeded, the library sets mcp.tool.cost.budget_exceeded on the span and lets the request through. It does not throw, block, or retry.
이 부분은 반론의 여지가 있을 수 있으므로, 그 근거를 설명하겠습니다. 요청을 거부(reject)할 수 있는 계측 라이브러리(instrumentation library)는 프로덕션 환경을 중단시킬 수 있는 라이브러리입니다. 강제 적용(Enforcement)은 요청 경로에 의도적으로 위치하며, 서버를 재배포하지 않고도 끌 수 있는 AI 게이트웨이(AI gateway)나 프록시(proxy)의 역할입니다. 계측(Instrumentation)은 영원히 켜두어도 안전해야 합니다.
절대로 예외를 발생시키지 않습니다.
알 수 없는 모델, 잘못된 형식의 사용량 페이로드(usage payload), 누락된 세션 ID, 응답 내의 쓰레기 데이터(garbage in) 등 그 어떤 것도 예외(exception)를 발생시키지 않습니다. 라이브러리는 해결할 수 있는 정보는 무엇이든 기록하고 다음 단계로 넘어갑니다. 장애의 원인이 되는 관측성(Observability) 도구는 그 자체의 목적을 상실한 것입니다.
예산 추적은 인메모리(in-memory) 방식입니다.
세션 예산은 프로세스별로 추적되며 인스턴스 간에 분산되지 않습니다. 로드 밸런서(load balancer) 뒤에서는 각 인스턴스가 자신만의 뷰(view)를 추적합니다.
이는 명확한 한계점이며, 여러분이 프로덕션 환경에서 이를 발견하게 하기보다 차라리 솔직하게 밝히는 편이 낫다고 판단했습니다. 대안은 Redis 의존성을 추가하는 것이었지만, 이 라이브러리를 의존성 없이(dependency-free) 유지하는 것이 분산된 예산 정확도보다 저에게는 더 가치 있는 일이었습니다. 만약 클러스터 전체에 걸친 강제 적용이 필요하다면, 대신 백엔드에서 mcp.tool.cost.total 카운터(counter)를 집계하십시오. 카운터가 존재하는 목적이 바로 그것입니다.
추출(Extraction)은 플러그인 방식입니다.
모든 제공업체의 응답 스키마(response schema)를 내부적으로 영원히 지원하려고 시도하는 대신, 사용량 추출기(usage extractor)는 완전히 교체할 수 있는 공개 API(public API)로 제공됩니다.
왜 이것이 빌링 대시보드(billing dashboard)가 아닌 트레이스(traces)에 포함되어야 하는가
AI FinOps는 그 자체로 하나의 전문 분야가 되어가고 있으며, 이와 관련된 대부분의 도구는 계정별 지출, 서비스별 지출, 태그별 지출과 같은 일일 집계(daily aggregates)를 기반으로 작동합니다.
집계 데이터는 재무(finance) 측면에서는 괜찮습니다. 하지만 엔지니어링 측면에서는 거의 무용지물입니다. 모델 지출이 급증했을 때, "어느 날"인지는 도움이 되지 않습니다. "어떤 워크플로우(workflow)에 의해 호출된 어떤 도구가, 어떤 모델을 사용하여, 어떤 세션에서 사용되었는가"가 실제로 문제를 해결할 수 있게 해주는 핵심 정보입니다.
비용을 지연 시간 (latency) 및 에러와 동일한 스팬 (span)에 배치한다는 것은, 이들을 별개의 조사 대상으로 취급하지 않음을 의미합니다. 느린 도구와 비용이 많이 드는 도구는 종종 동일한 도구이며, 이제 여러분은 단 한 번의 쿼리로 이를 확인할 수 있습니다.
자주 묻는 질문
이 기능이 도구 호출에 지연 시간 (latency)을 추가하나요?
비용 계산은 응답에 이미 존재하는 데이터에 대한 테이블 조회 (table lookup) 및 산술 연산입니다. 네트워크 호출이나 외부 서비스 호출은 발생하지 않습니다.
사용 중인 모델이 가격표 (pricing table)에 없다면 어떻게 되나요?
스팬 (span)에는 여전히 토큰과 모델 이름이 기록됩니다. 비용은 추측하는 대신 생략되며, 예외 (exception)가 발생하지는 않습니다.
기존의 OpenTelemetry GenAI 대시보드와 함께 사용할 수 있나요?
네, 가능합니다. 이 라이브러리는 기존 대시보드들이 변경 없이 MCP 스팬을 인식할 수 있도록 mcp.tool.model과 함께 gen_ai.response.model을 함께 방출 (emit)합니다.
Bedrock 기반이 아닌 MCP 서버에서도 사용할 수 있나요?
네. Anthropic, OpenAI, Gemini, DeepSeek의 가격 정보가 기본적으로 제공되며, 커스텀 가격 설정을 통해 그 외의 모든 경우를 커버할 수 있습니다.
예산을 초과하면 요청을 차단하나요?
아니요. 스팬 (span)에 mcp.tool.cost.budget_exceeded를 기록하고 계속 진행합니다.
다음 단계
향후 릴리스를 위해 고려 중인 방향은 다음과 같습니다:
- 비용 인식 샘플링 (cost-aware sampling): 비용이 많이 드는 트레이스 (trace)는 샘플링 결정에서 살아남고, 저렴한 트레이스는 제외되도록 함
- 멀티 홉 (multi-hop) MCP 토폴로지를 위한 서버 간 트레이스 상관관계 (cross-server trace correlation)
- OpenTelemetry Events를 통한 예측 예산 알림
- 더 풍부한 AI FinOps 대시보드 템플릿
목표는 AI 게이트웨이가 되는 것이 아닙니다. 여러분이 트레이스 (trace)를 열었을 때, 비용 정보가 이미 그곳에 놓여 있도록 만드는 것입니다.
사용해 보기
bash
npm install opentel-mcp
GitHub: https://github.com/Thirumalaiboobathi/opentel-mcp — MIT 라이선스이며, 이슈 (issue)와 PR을 환영합니다. 별 (star) 하나는 더 많은 사람들이 이 프로젝트를 찾는 데 도움이 됩니다.
npm: https://www.npmjs.com/package/opentel-mcp
CHANGELOG: https://github.com/Thirumalaiboobathi/opentel-mcp/blob/main/CHANGELOG.md
모든 AI 제공업체(AI provider)는 각자 고유한 형태의 사용량(usage) 데이터를 반환하며, 사용자들이 보고하는 예외 사례(edge cases)들이 다음 릴리스의 형태를 결정합니다. 만약 도구 결과(tool results)가 깔끔하게 파싱되지 않는다면, 페이로드 형태(payload shape)를 댓글로 남기거나 이슈(issue)를 생성해 주세요. 그것이 귀하의 제공업체를 지원받을 수 있는 가장 빠른 방법입니다.
저는 MCP 생태계를 위한 오픈 소스 관측성(observability) 도구를 구축하고 있습니다. 이 시리즈의 다음 주제는 다음과 같습니다: 왜 CallToolResult.isError가 트레이스(traces)에서 소리 없이 사라지는지, 그리고 제가 Python 측에서 이를 찾으려 했을 때 무엇을 발견했는지에 대해 다룹니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기