Ir al contenido
mcpd
sin daemon
Código

Contabilidad de costos

Una cifra de costo solo vale si puedes decir de dónde salió. Aquí está cada paso entre un recuento de tokens y un número en la pantalla.

Se tarifa una vez, en la ingesta

Cuando llega un span con un id de modelo y un recuento de tokens, mcpd busca ese modelo en un libro de precios, calcula lo que costó la llamada y guarda el resultado en el span. Nada se vuelve a calcular al leer.

Esa es la decisión importante de esta página. Los precios cambian. Si el costo se dedujera en el momento de la consulta a partir del libro que estuviera cargado ese día, una bajada de tarifas el mes que viene reescribiría en silencio lo que costó el mes pasado, y el número que le reportaste a alguien ya no estaría cuando fuera a comprobarlo. Tarifar en la ingesta congela la cifra contra las tarifas que eran ciertas cuando se hizo la llamada. Cada costo almacenado anota además el id del libro que lo produjo, así que una cifra se puede rastrear hasta las tarifas que hay detrás.

Un span que ya viene con un costo se deja exactamente como llegó. Si tu proveedor te da una cifra real y la registras, mcpd conserva la tuya y no tarifa por encima. El flag is_estimated separa las dos: todo lo que mcpd dedujo de un libro se marca como estimado, porque lo es.

Las tarifas son por millón de tokens

Toda tarifa de un libro de precios está en unidades de moneda por millón de tokens, que es como las publican todos los proveedores. Una tarifa copiada de una página de precios entra en un libro sin cambios, sin ninguna división que equivocar a las tres de la mañana. La aritmética de un medidor es tokens × rate ÷ 1,000,000.

El costo vive en el paso, no en la ejecución

Cada llamada al modelo lleva su propia cifra. El total de una ejecución es la suma de sus spans, y mcpd recalcula esa suma a partir de los spans cada vez que llega uno, en lugar de incrementar un contador. Los spans llegan desordenados, se reintentan y se actualizan en el sitio, y un total que se incrementa se desvía con las tres cosas. Un número de cabecera que no cuadra con las filas de debajo destruye la confianza en todos los demás números de la página.

Trace b3d9e71a Fault
0ms 2.1s 4.2s 6.3s 8.4s
  1. agent support_agent fail
  2. model chat
  3. tool returns_policy
  4. model chat
  5. tool returns_policy
  6. model chat
  7. tool returns_policy

The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.

Las tres llamadas al modelo de arriba están tarifadas en $0.00015, $0.00018 y $0.00021. El total de la ejecución en la cabecera es su suma y nada más: las llamadas a herramientas, la consulta a memoria y el guardrail no consumieron tokens, así que no llevan costo y no aportan ninguno.

Los medidores

Una llamada al modelo no es un solo número de tokens. Los proveedores facturan varios medidores a tarifas distintas, y un libro de precios da una tarifa para cada uno.

TarifaSe aplica aCuando el libro la omite
input Tokens de entrada normales. Obligatoria.
output Tokens de respuesta normales. Obligatoria.
cache_read Tokens servidos desde una caché de prompts. Se cobran a input.
cache_write Tokens escritos en una caché de prompts. Se cobran a input.
cache_write_5m Escrituras en una caché de cinco minutos, donde el proveedor tarifa aparte cada TTL. Recae en cache_write, y luego en input.
cache_write_1h Escrituras en una caché de una hora. Recae en cache_write, y luego en input.
audio_input Tokens de audio de entrada. Se cobran a input.
audio_output Tokens de audio de salida. Se cobran a output.
reasoning Tokens de razonamiento oculto, donde un proveedor los factura aparte. Sin reserva. Se cobran a output como parte del total de salida, que es como los facturan los grandes proveedores, salvo que el proveedor haya reportado además un recuento text aparte. Mira más abajo.

Un token en caché se cobra una vez

La forma clásica de inflar una factura es cobrar dos veces un token en caché: una a la tarifa de caché, y otra como parte del total de entrada reportado dentro del cual ya está. Así que cuando un proveedor reporta un desglose, ese desglose manda. mcpd tarifa cada medidor a su propia tarifa y cobra solo el resto a la tarifa de entrada llana.

Una llamada a claude-sonnet-4 que reporta un millón de tokens de entrada, de los cuales novecientos mil se leyeron de la caché, se tarifa contra el libro integrado así:

  • 900,000 lecturas de caché a $0.30 por millón, que son $0.27.
  • 100,000 tokens de entrada restantes a $3.00 por millón, que son $0.30.
  • $0.57 de costo de entrada, en lugar de los $3.00 que habría producido el total reportado por su cuenta.

Donde un proveedor da un recuento text explícito, ese recuento se usa como resto directamente. Donde no lo da, el resto es el total reportado menos todos los campos medidos.

Eso deja un hueco que conviene nombrar. Un medidor para el que el libro no tiene tarifa se cobra solo como parte del resto, así que cuando el proveedor envió además un recuento text no se cobra en absoluto. Los tokens de razonamiento caen ahí siempre que tu libro no les dé una tarifa propia, y declarar una lo arregla. Los tokens de imagen caen ahí siempre, porque un libro no tiene ninguna tarifa de imagen que dar, así que en una carga muy multimodal lee la cifra como baja y no como exacta.

Un modelo desconocido no tiene costo, no un costo de cero

Si el libro no tiene tarifas para el modelo, mcpd guarda el span sin ningún campo de costo. No guarda un cero.

Cero es una afirmación: esta llamada fue gratis. Ausente es una admisión: no sabemos qué costó esta llamada. Llevan a decisiones distintas, y una herramienta que reporta la segunda como la primera está mintiendo sobre una factura. La misma regla recorre la ruta de lectura, donde una ejecución cuyos spans no suman nada se devuelve sin cifra de costo en lugar de con un costo de cero, así que una columna de costo en blanco significa sin tarifar y nunca gratis.

La consecuencia práctica conviene interiorizarla: un modelo que falta en el libro falta en todos los totales. Su gasto no aparece en /v1/api/analytics/cost, y sus ejecuciones quedan excluidas por cualquier filtro min_cost positivo. Un total semanal que se ve demasiado bajo suele ser un id de modelo que no resuelve, no una semana tranquila.

Resolver un id de modelo

El id de un span es el que envió tu cliente, y los clientes lo decoran. Una pasarela le antepone su proveedor, una nube le antepone su región, un proveedor le fija una fecha de publicación. Un libro se indexa por nombres de modelo desnudos, así que la búsqueda prueba cuatro cosas en orden y para en el primer acierto.

PasoEjemplo
El id tal cual, normalizado a minúsculas GPT-4o encuentra gpt-4o
La parte posterior a la última barra openai/gpt-4o encuentra gpt-4o
La cola con puntos de un id de nube us.anthropic.claude-sonnet-4 encuentra claude-sonnet-4
El id sin la fecha final, con su prefijo o sin él openai/gpt-4o-2024-08-06 encuentra gpt-4o

Se reconocen los sufijos -YYYY-MM-DD y -YYYYMMDD. Fallar los cuatro pasos es un desenlace normal y no un error: significa sin costo, como arriba.

El libro que se incluye

mcpd lleva un pequeño libro integrado para que docker run produzca cifras reales sin ninguna configuración. Se llama builtin, tarifa en USD, y todos los costos que produce anotan ese nombre.

ModeloInputOutputLectura de cachéEscritura en caché, 5 minEscritura en caché, 1 hora
claude-opus-415.0075.001.5018.7530.00
claude-opus-4-115.0075.001.5018.7530.00
claude-opus-4-55.0025.000.506.2510.00
claude-sonnet-43.0015.000.303.756.00
claude-sonnet-4-53.0015.000.303.756.00
claude-haiku-4-51.005.000.101.252.00
claude-3-5-haiku0.804.000.081.001.60
gpt-4o2.5010.001.25a inputa input
gpt-4o-mini0.150.600.075a inputa input
gpt-4.12.008.000.50a inputa input
gpt-4.1-mini0.401.600.10a inputa input
o32.008.000.50a inputa input
o4-mini1.104.400.275a inputa input
gemini-2.5-pro1.2510.000.31a inputa input
gemini-2.5-flash0.302.500.075a inputa input
text-embedding-3-small0.020.00a inputa inputa input
text-embedding-3-large0.130.00a inputa inputa input

USD por millón de tokens, tal como estaba el libro la última vez que se contrastó con un catálogo de modelos de terceros, fecha que el libro anota como su as_of. Las entradas de Anthropic llevan las dos tarifas de escritura en caché porque el daemon recae de la tarifa de una hora en la de cinco minutos, y un libro que omitiera la larga cobraría de menos en silencio. OpenAI no cobra nada por escribir en su caché, así que allí una escritura en caché se factura a la tarifa de entrada. gemini-2.5-flash lleva además una tarifa de audio de entrada de 1.00; el resto de las entradas tarifan el audio a su tarifa de texto. No existe claude-haiku-4: el id que sirve Anthropic es claude-haiku-4-5, y un id que no resuelve a nada se queda sin tarifar en lugar de adivinado.

Estos son precios de lista publicados, no tus precios. Si tienes tarifas negociadas, o ejecutas un modelo que no está en esta tabla, las cifras que te enseña mcpd son una estimación y hay que leerlas como tal. El libro es corto a propósito: uno largo se quedaría desactualizado en más sitios y daría una impresión de exactitud mayor de la que merece.

Aportar tu propio libro todavía no tiene flag de línea de comandos. El libro es parte del estado del servidor, y hoy la forma de reemplazarlo es incrustar mcpd como biblioteca y pasarle uno a AppState::with_price_book. Un libro es un id, una moneda ISO 4217 y un mapa de id de modelo a tarifas, y cada cifra que produce lleva estampado su id para poder distinguir dos libros después. Los precios de un proyecto deberían compartir una moneda; mcpd suma los spans de una ejecución aritméticamente y no convierte entre monedas.

Leer el dinero de vuelta

GET /v1/api/analytics/cost responde adónde fue el dinero durante una ventana, sin exportar nada a una hoja de cálculo.

curl -s 'http://localhost:4317/v1/api/analytics/cost?from=-7d&group_by=model'
ParámetroSignificado
from, to La ventana. RFC 3339 o la forma abreviada relativa. Por defecto, los últimos siete días, terminando ahora.
group_by model por defecto. También provider, entity_name, environment, status, session_id, user_id, service_name o name.
bucket 30s, 5m, 1h o 1d. Omítelo para tener solo totales; dalo para obtener además una serie temporal.
project Qué proyecto leer. Si lo omites, el proyecto por defecto del daemon.

adónde fue

# Where the money went last week, by model.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model'

# The same week as a daily series, so you can see the day it changed.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
  --data-urlencode 'from=-7d' \
  --data-urlencode 'group_by=model' \
  --data-urlencode 'bucket=1d'

Los grupos vuelven de mayor a menor, con un tope de cien filas, y una serie por intervalos tiene un tope de cinco mil puntos.

respuesta

{
  "from": "2026-08-20T09:00:00Z",
  "to": "2026-08-27T09:00:00Z",
  "currency": "USD",
  "total_cost": 12.418264,
  "groups": [
    {
      "key": "claude-sonnet-4",
      "count": 1841,
      "error_count": 12,
      "total_cost": 9.612055,
      "input_tokens": 2903118,
      "output_tokens": 214477,
      "latency": { "mean": 1840.2, "max": 24193.0 }
    },
    {
      "key": "gpt-4o-mini",
      "count": 6204,
      "error_count": 3,
      "total_cost": 2.806209,
      "input_tokens": 9114882,
      "output_tokens": 388310,
      "latency": { "mean": 612.4, "max": 8801.0 }
    }
  ]
}

Una sutileza en count. Agrupar por model, provider o entity_name lee los spans, así que count es un número de llamadas al modelo. Agrupar por cualquier otra cosa lee la proyección de la ejecución, así que count es un número de ejecuciones. Los dos sirven; no son el mismo número, y compararlos entre sí es el error más fácil de cometer aquí.

La latencia por grupo de esta respuesta lleva solo una media y un máximo. Los percentiles vienen de GET /v1/api/analytics/summary, que los calcula sobre la ventana.

Adónde ir después

El costo es una columna de una lista de ejecuciones, y la lista de ejecuciones es donde empieza de verdad una investigación: Encontrar una ejecución cubre min_cost y la ordenación por gasto. Si tus cifras se ven mal porque los payloads se están descartando antes de tarifarlos, revisa Muestreo, que decide qué se registra siquiera. Todos los campos nombrados en esta página están listados en el modelo de datos.