Skip to content
mcpd
no daemon
Source

Forwarding

mcpd speaks OTLP in both directions. Nothing that arrives is stuck here.

One protocol out, not eleven exporters

mcpd receives OTLP, and it can emit OTLP. That single property replaces most of what a set of per-vendor exporters does: Langfuse, Datadog, Honeycomb, Grafana, and every other destination worth naming accept OTLP over HTTP. Forwarding is therefore one implementation rather than eleven, and it cannot fall behind a vendor's SDK release.

It also means adopting mcpd is reversible. Point it at whatever you were using before, run both for a fortnight, and decide with the data in front of you rather than on a migration you cannot undo.

Turning it on

store locally and copy onward

mcpd serve --forward-endpoint https://otlp.example.com

The path is appended, so give the base URL and not /v1/traces. For a destination that needs credentials, add headers:

with an authorization header

mcpd serve \ --forward-endpoint https://otlp.example.com \ --forward-header 'Authorization: Bearer $TOKEN'

The same settings live under forward in the config file, where batch_size and enabled are also available. A flag beats the file, so one instance can be pointed elsewhere without editing a shared ConfigMap.

{
  "forward": {
    "endpoint": "https://otlp.example.com",
    "headers": [["Authorization", "Bearer sk-..."]],
    "batch_size": 256,
    "enabled": true
  }
}
Ingest pipeline
  1. Decode

    OTLP or native, gzip or plain

  2. Sample

    Decided once, at the root

  3. Redact

    Before anything is written

  4. Price

    From the configured price book

  5. Store

    One transaction per batch

Redaction is stage three, not a cleanup job. A value that a rule matches never reaches storage at all.

Local storage happens first

A span is written to mcpd's own store before anything is forwarded, and the forward runs on its own task afterwards. Two consequences, both deliberate.

A slow destination never adds its latency to the request that produced the span. Your agent does not wait on someone else's collector.

A destination that is down never turns into a rejected ingest. mcpd keeps working and retries the forward on its own task. The alternative, where another company's outage means your telemetry is not kept at all, is the exact failure this design exists to avoid.

Each batch gets three attempts. A 429, a 5xx or a failed connection is retried after 250 ms, and the wait doubles after each attempt. A Retry-After given in seconds replaces the wait, up to 30 seconds. Any other 4xx is final on the first answer, because the batch will stay malformed for that destination. When the daemon is stopping, it makes the remaining attempts at once. A batch that is refused for good, or is still undelivered after its last attempt, is logged, and its spans are added to mcpd_forward_dropped_spans_total, so you can see how much of the copy is missing.

A destination that accepts a connection and never answers cannot take the daemon's memory either. At most 64 ingest requests' copies wait on the destination at once. While that many are waiting, a new copy is dropped at once and counted in mcpd_forward_dropped_spans_total, and the local write is unaffected.

There is no durable outbound queue. A batch waits in memory while it is retried, and when the forwarding queue is full, a new batch is dropped and its spans are counted in the same metric. A durable queue is a different product, and a silent one that falls hours behind is worse than a visible gap. If you need guaranteed delivery, put a real collector between mcpd and the destination and let it do the job it was built for.

Redaction applies to what leaves

Forwarding happens after the intake pipeline, which redacts in place before anything is written. Whatever mcpd was told to hide never reaches the next hop either. A rule that protected your storage but not your egress would not be a privacy control.

What gets forwarded

Spans. Logs, metrics, scores, feedback and aspect notes stay in mcpd, because the destinations people forward to are tracing backends and would drop the rest or mangle it into attributes.

Spans are grouped by service into resourceSpans, the shape a receiver expects, rather than one resource per span, which is valid and makes every downstream regroup it.

mcpd forwarding to mcpd

Because both ends speak the same protocol, a local mcpd can forward to a shared one. Developers keep a fast store on their own machine with full payloads, and the team's instance gets the same runs with its own retention and its own redaction rules applied on arrival.

Next