How the MCP Telemetry System Sends Usage Data to Google Clearcut
The MCP telemetry system uses a watchdog subprocess to batch events and POST them to Google Clearcut's logging API at 15-minute intervals, with automatic rate-limit handling and retry logic.
The Chrome DevTools MCP (Model Context Protocol) server implements a privacy-conscious telemetry pipeline that buffers usage data locally before transmitting it to Google Clearcut. This article examines the complete data flow—from event generation in the main process to the final HTTP POST—based on the actual implementation in the ChromeDevTools/chrome-devtools-mcp repository.
Architecture Overview of the MCP Telemetry Pipeline
The MCP telemetry system operates as a four-stage pipeline that isolates network I/O from the main server process. This design ensures that telemetry collection never blocks tool execution, even during network outages or high-latency scenarios.
The pipeline consists of:
- Event Generation:
ClearcutLoggercreates structured JSON events - Inter-Process Communication:
WatchdogClientstreams events via stdin to a detached subprocess - Buffering & Scheduling:
ClearcutSenderaccumulates events and manages flush timers - Network Transmission: Batched HTTP POST requests to
play.googleapis.com/log
Stage 1: Event Generation with ClearcutLogger
The ClearcutLogger class in src/telemetry/ClearcutLogger.ts serves as the primary interface for the MCP telemetry system. It exposes strongly-typed methods for recording specific event types while abstracting the underlying transport mechanism.
Initializing the Logger
When the MCP server starts in src/main.ts, it instantiates ClearcutLogger with configuration options that control the telemetry endpoint and flush behavior:
import { ClearcutLogger } from './src/telemetry/ClearcutLogger.js';
const telemetry = new ClearcutLogger({
appVersion: '1.2.3',
clearcutEndpoint: 'https://play.googleapis.com/log?format=json_proto',
clearcutForceFlushIntervalMs: 15 * 60 * 1000, // 15 minutes
clearcutIncludePidHeader: false,
});
Recording Tool Invocations
The logger provides specific methods for different telemetry events. When a DevTools tool executes, the server calls logToolInvocation():
await telemetry.logToolInvocation({
toolName: 'coverage',
success: true,
latencyMs: 124,
});
Internally, this method constructs a WatchdogMessage and forwards it to the WatchdogClient instance, converting the structured data into a JSON line written to the subprocess stdin.
Stage 2: Inter-Process Communication via WatchdogClient
The WatchdogClient class in src/telemetry/WatchdogClient.ts manages the lifecycle of the telemetry subprocess. By running the network client in a separate Node.js process, the MCP telemetry system ensures that crashes or hangs in the telemetry logic cannot affect the main MCP server.
Spawning the Watchdog Subprocess
The client spawns the watchdog entry point (watchdog/main.js) as a detached child process with a pipe on stdin:
import { spawn } from 'child_process';
import { fileURLToPath } from 'url';
const watchdogPath = fileURLToPath(
new URL('./watchdog/main.js', import.meta.url)
);
const child = spawn(process.execPath, [watchdogPath, ...cliArgs], {
stdio: ['pipe', 'ignore', 'ignore'],
detached: true,
});
The CLI arguments passed to the watchdog include the Clearcut endpoint URL, flush interval, and optional PID header flag.
Message Transport Protocol
Communication between the parent and watchdog uses newline-delimited JSON (NDJSON). When ClearcutLogger records an event, WatchdogClient.send() serializes the message:
// Inside ClearcutLogger.logToolInvocation()
this.#watchdog.send({
type: WatchdogMessageType.LOG_EVENT,
payload: {
tool_invocation: {
tool_name: args.toolName,
success: args.success,
latency_ms: args.latencyMs,
},
},
});
The watchdog process reads these lines using a readline.Interface and forwards valid events to the ClearcutSender for buffering and transmission.
Stage 3: Event Buffering and Session Management
Inside the watchdog subprocess, ClearcutSender (located in src/telemetry/watchdog/ClearcutSender.ts) implements the client-side buffering logic. This component ensures efficient network usage by aggregating multiple telemetry events into single HTTP requests.
The ClearcutSender Buffer
The sender maintains an in-memory array with a maximum capacity of 1000 events (MAX_BUFFER_SIZE). When the buffer reaches capacity, the oldest events are dropped to prevent unbounded memory growth:
private enqueueEvent(event: TelemetryEvent): void {
if (this.#buffer.length >= MAX_BUFFER_SIZE) {
this.#buffer.shift(); // Drop oldest event
}
const enrichedEvent = {
...event,
session_id: this.#sessionId,
app_version: this.#appVersion,
os_type: this.#osType,
};
this.#buffer.push(enrichedEvent);
if (!this.#flushScheduled) {
this.#scheduleFlush();
}
}
Session Decoration and Metadata
Every event is enriched with session metadata before buffering. The session_id is generated once per watchdog process using crypto.randomUUID(), ensuring that events from a single MCP server instance can be correlated. The app_version and os_type are derived from CLI arguments passed during watchdog initialization.
Stage 4: HTTP Transmission to Google Clearcut
The final stage occurs when the flush timer fires or the buffer reaches capacity. The ClearcutSender.#sendBatch() method handles the HTTP POST to Google Clearcut's logging infrastructure.
Batch Request Format
The sender constructs a LogRequest payload conforming to the Clearcut JSON-proto format. The request includes a log_source identifier (2839 for Chrome DevTools MCP), client_info, and an array of log_event objects:
const requestBody: LogRequest = {
log_source: LOG_SOURCE, // 2839
request_time_ms: Date.now().toString(),
client_info: { client_type: CLIENT_TYPE }, // 47
log_event: events.map(({event, timestamp}) => ({
event_time_ms: timestamp.toString(),
source_extension_json: JSON.stringify(event),
})),
};
await fetch(this.#clearcutEndpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...(this.#includePidHeader ? { 'X-Watchdog-Pid': process.pid.toString() } : {}),
},
body: JSON.stringify(requestBody),
signal: controller.signal,
});
The source_extension_json field contains the stringified telemetry event, allowing Clearcut to store arbitrary JSON structures while maintaining a consistent schema at the transport layer.
Rate Limiting and Retry Logic
The MCP telemetry system implements sophisticated back-off handling to respect Clearcut's rate limits. When the server responds with a next_request_wait_millis value, the sender adjusts its flush interval accordingly, with a minimum wait time of 30 seconds between requests:
- Transient errors (5xx, network timeouts): Events are re-queued for the next flush attempt
- Permanent errors (4xx client errors): Events are discarded to prevent infinite retry loops
- Timeout handling: Each request uses an
AbortControllerwith a 30-second timeout to prevent hanging connections
Shutdown Flushing
When the MCP server shuts down, the watchdog process detects the stdin closure and triggers a final flush. The sendShutdownEvent() method enqueues a server_shutdown event and calls #finalFlush(), which bypasses the normal scheduling logic to ensure all pending telemetry reaches Clearcut before the process exits:
// In watchdog/main.ts
rl.on('close', () => {
sender.sendShutdownEvent();
setTimeout(() => process.exit(0), 5000); // Allow 5s for final flush
});
Summary
The MCP telemetry system in Chrome DevTools implements a robust, multi-process pipeline for sending usage data to Google Clearcut:
- Process isolation prevents telemetry operations from impacting the main MCP server performance
- Batching and buffering reduce network overhead by aggregating up to 1000 events per HTTP request
- Automatic retry and rate-limit handling ensure reliable delivery without overwhelming the Clearcut endpoint
- Graceful shutdown flushing guarantees that final events are transmitted before process termination
- Configurable endpoints allow for testing and custom telemetry backends via CLI arguments
Frequently Asked Questions
What is the default flush interval for MCP telemetry events?
The default flush interval is 15 minutes (900,000 milliseconds). This value is passed to the watchdog subprocess via the --clearcut-force-flush-interval-ms CLI argument and can be overridden when initializing the ClearcutLogger for testing or specific deployment requirements.
How does the MCP telemetry system handle network failures?
The system implements a multi-layered resilience strategy. ClearcutSender distinguishes between transient errors (5xx status codes, timeouts) and permanent errors (4xx client errors). Transient failures trigger re-queueing of events for the next flush cycle, while permanent failures result in event discard to prevent infinite loops. Additionally, the system respects server-side rate limiting via the next_request_wait_millis response field, enforcing a minimum 30-second wait between requests.
Can the Clearcut endpoint be customized for testing?
Yes, the endpoint is fully configurable. When creating a ClearcutLogger instance, you can pass clearcutEndpoint in the constructor options to override the default https://play.googleapis.com/log?format=json_proto. This is particularly useful for integration testing or when routing telemetry through a proxy. Additionally, the clearcutIncludePidHeader option enables the X-Watchdog-Pid header for process tracking in test environments.
What happens to telemetry events when the MCP server shuts down?
During shutdown, the watchdog process detects the closure of stdin from the parent process and triggers a graceful final flush. The sendShutdownEvent() method enqueues a server_shutdown event and invokes #finalFlush(), which bypasses the normal scheduling timer to immediately attempt transmission of all buffered events. The process waits up to 5 seconds (configurable via shutdown timeout) to allow the HTTP request to complete before exiting, ensuring minimal data loss during routine restarts or updates.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →