@mcploom/analytics 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,19 @@
1
1
  # @mcploom/analytics
2
2
 
3
- Lightweight analytics and observability for [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) servers. Zero required dependencies, framework-agnostic, works at the JSON-RPC transport level.
3
+ Lightweight analytics and observability for [Model Context Protocol](https://modelcontextprotocol.io/) servers. Zero required dependencies, framework-agnostic, and works at the JSON-RPC transport layer.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/%40mcploom%2Fanalytics?style=flat-square)](https://www.npmjs.com/package/@mcploom/analytics)
6
+ [![License](https://img.shields.io/github/license/aallam/mcploom?style=flat-square)](https://github.com/aallam/mcploom/blob/main/LICENSE)
7
+ [![Examples](https://img.shields.io/badge/examples-analytics-0ea5e9?style=flat-square)](https://github.com/aallam/mcploom/tree/main/examples)
8
+ [![CI](https://img.shields.io/github/actions/workflow/status/aallam/mcploom/ci.yml?branch=main&style=flat-square&label=CI)](https://github.com/aallam/mcploom/actions/workflows/ci.yml)
9
+
10
+ ## Examples
11
+
12
+ - [Track plain handlers](https://github.com/aallam/mcploom/blob/main/examples/analytics-track-handlers.ts)
13
+ - [Instrument an MCP transport](https://github.com/aallam/mcploom/blob/main/examples/analytics-instrument-transport.ts)
14
+ - [Use a custom exporter](https://github.com/aallam/mcploom/blob/main/examples/analytics-custom-exporter.ts)
15
+ - [Express integration](https://github.com/aallam/mcploom/blob/main/examples/analytics-express.ts)
16
+ - [Full examples index](https://github.com/aallam/mcploom/tree/main/examples)
4
17
 
5
18
  ## Features
6
19
 
package/dist/index.cjs CHANGED
@@ -43,6 +43,9 @@ var Collector = class {
43
43
  flushInFlight;
44
44
  toolWindowSize;
45
45
  onFlushError;
46
+ /**
47
+ * Creates an event collector with bounded buffering and optional periodic flushing.
48
+ */
46
49
  constructor(maxBufferSize, exporter, flushIntervalMs, options = {}) {
47
50
  this.maxBufferSize = maxBufferSize;
48
51
  this.exporter = exporter;
@@ -596,6 +599,9 @@ var McpAnalytics = class {
596
599
  metadata;
597
600
  tracing;
598
601
  samplingStrategy;
602
+ /**
603
+ * Creates an analytics instance with the configured exporter, buffering, and sampling behavior.
604
+ */
599
605
  constructor(config) {
600
606
  this.sampleRate = config.sampleRate ?? 1;
601
607
  this.metadata = config.metadata;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["maxBufferSize: number","exporter: ExporterFn","tools: Record<string, ToolStats>","sessions: Record<string, SessionStats>","lines: string[]","tracerPromise: Promise<OtlpTracer> | undefined","otelApi: OtelApi | undefined","pendingCall: PendingCall","outputPayload: unknown","event: ToolCallEvent"],"sources":["../src/utils.ts","../src/collector.ts","../src/exporters/console.ts","../src/exporters/custom.ts","../src/exporters/json.ts","../src/exporters/otlp.ts","../src/tracing.ts","../src/middleware.ts","../src/analytics.ts"],"sourcesContent":["/**\n * Returns the byte length of a string (UTF-8).\n */\nexport function byteSize(value: unknown): number {\n if (value === undefined || value === null) return 0;\n const str = typeof value === \"string\" ? value : JSON.stringify(value);\n return new TextEncoder().encode(str).byteLength;\n}\n\n/**\n * Compute a percentile from a sorted array of numbers.\n * Uses linear interpolation between the closest ranks.\n */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n if (sorted.length === 1) return sorted[0];\n\n const index = (p / 100) * (sorted.length - 1);\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n\n if (lower === upper) return sorted[lower];\n\n const weight = index - lower;\n return sorted[lower] * (1 - weight) + sorted[upper] * weight;\n}\n\n/**\n * Insert a value into an already-sorted array (ascending), maintaining sort order.\n */\nexport function sortedInsert(arr: number[], value: number): void {\n let lo = 0;\n let hi = arr.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (arr[mid] < value) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n arr.splice(lo, 0, value);\n}\n","import type {\n AnalyticsSnapshot,\n ExporterFn,\n SessionStats,\n ToolCallEvent,\n ToolStats,\n} from \"./types.js\";\nimport { percentile } from \"./utils.js\";\n\n/**\n * Per-tool accumulator for computing stats without scanning the full buffer.\n */\ninterface ToolAccumulator {\n count: number;\n errorCount: number;\n totalMs: number;\n /** Sorted durations for percentile computation */\n durations: number[];\n lastCalledAt: number;\n}\n\n/**\n * Per-session accumulator.\n */\ninterface SessionAccumulator extends ToolAccumulator {\n tools: Map<string, ToolAccumulator>;\n}\n\ninterface CollectorOptions {\n toolWindowSize?: number;\n onFlushError?: (error: unknown) => void;\n}\n\n/**\n * In-memory ring buffer that collects ToolCallEvents, computes stats,\n * and periodically flushes to an exporter.\n */\nexport class Collector {\n private readonly buffer: ToolCallEvent[] = [];\n private readonly accumulators = new Map<string, ToolAccumulator>();\n private readonly sessionAccumulators = new Map<string, SessionAccumulator>();\n private totalCalls = 0;\n private totalErrors = 0;\n private readonly startTime = Date.now();\n\n private flushTimer: ReturnType<typeof setInterval> | undefined;\n /** Events accumulated since last flush, to be sent to the exporter */\n private pending: ToolCallEvent[] = [];\n private flushInFlight: Promise<void> | undefined;\n private readonly toolWindowSize: number;\n private readonly onFlushError: (error: unknown) => void;\n\n constructor(\n private readonly maxBufferSize: number,\n private readonly exporter: ExporterFn,\n flushIntervalMs: number,\n options: CollectorOptions = {},\n ) {\n this.toolWindowSize = Math.max(1, options.toolWindowSize ?? 2_048);\n this.onFlushError =\n options.onFlushError ??\n ((error) => {\n console.error(\"[McpAnalytics] Exporter flush failed:\", error);\n });\n\n if (flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush().catch((error) => {\n this.onFlushError(error);\n });\n }, flushIntervalMs);\n // Don't hold the process open for analytics flushing\n if (\n this.flushTimer &&\n typeof this.flushTimer === \"object\" &&\n \"unref\" in this.flushTimer\n ) {\n this.flushTimer.unref();\n }\n }\n }\n\n /**\n * Record a new tool call event.\n */\n record(event: ToolCallEvent): void {\n // Ring buffer: drop oldest when full\n if (this.maxBufferSize > 0) {\n if (this.buffer.length >= this.maxBufferSize) {\n this.buffer.shift();\n }\n this.buffer.push(event);\n }\n this.pending.push(event);\n\n this.totalCalls++;\n if (!event.success) this.totalErrors++;\n\n // Update per-tool accumulator\n this.updateToolAccumulator(this.accumulators, event.toolName, event);\n\n // Update per-session accumulator\n const sessionKey = event.sessionId ?? \"unknown\";\n let sessionAcc = this.sessionAccumulators.get(sessionKey);\n if (!sessionAcc) {\n sessionAcc = { ...this.newAccumulator(), tools: new Map() };\n this.sessionAccumulators.set(sessionKey, sessionAcc);\n }\n this.updateAccumulator(sessionAcc, event);\n this.updateToolAccumulator(sessionAcc.tools, event.toolName, event);\n }\n\n /**\n * Get aggregated stats for all tools.\n */\n getStats(): AnalyticsSnapshot {\n const tools: Record<string, ToolStats> = {};\n for (const [name, acc] of this.accumulators) {\n tools[name] = this.accToStats(acc);\n }\n\n const sessions: Record<string, SessionStats> = {};\n for (const [sessionId, acc] of this.sessionAccumulators) {\n sessions[sessionId] = this.sessionAccToStats(acc);\n }\n\n return {\n totalCalls: this.totalCalls,\n totalErrors: this.totalErrors,\n errorRate: this.totalCalls > 0 ? this.totalErrors / this.totalCalls : 0,\n uptimeMs: Date.now() - this.startTime,\n tools,\n sessions,\n };\n }\n\n /**\n * Get stats for a single tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n const acc = this.accumulators.get(toolName);\n if (!acc) return undefined;\n return this.accToStats(acc);\n }\n\n /**\n * Get stats for a single session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n const acc = this.sessionAccumulators.get(sessionId);\n if (!acc) return undefined;\n return this.sessionAccToStats(acc);\n }\n\n /**\n * Get top sessions ordered by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n if (limit <= 0) return [];\n return [...this.sessionAccumulators.entries()]\n .sort((a, b) => {\n const byCount = b[1].count - a[1].count;\n if (byCount !== 0) return byCount;\n return b[1].lastCalledAt - a[1].lastCalledAt;\n })\n .slice(0, limit)\n .map(([sessionId, acc]) => ({\n sessionId,\n stats: this.sessionAccToStats(acc),\n }));\n }\n\n /**\n * Flush pending events to the exporter.\n */\n async flush(): Promise<void> {\n if (this.flushInFlight) {\n await this.flushInFlight;\n return;\n }\n\n const run = this.flushPending();\n this.flushInFlight = run;\n try {\n await run;\n } finally {\n if (this.flushInFlight === run) {\n this.flushInFlight = undefined;\n }\n }\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.buffer.length = 0;\n this.pending.length = 0;\n this.accumulators.clear();\n this.sessionAccumulators.clear();\n this.totalCalls = 0;\n this.totalErrors = 0;\n }\n\n /**\n * Stop the flush timer and flush remaining events.\n */\n async destroy(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = undefined;\n }\n await this.flush();\n }\n\n private async flushPending(): Promise<void> {\n while (this.pending.length > 0) {\n const batch = this.pending;\n this.pending = [];\n try {\n await this.exporter(batch);\n } catch (error) {\n // Re-queue batch to avoid data loss on transient exporter failures.\n this.pending = batch.concat(this.pending);\n throw error;\n }\n }\n }\n\n private newAccumulator(): ToolAccumulator {\n return {\n count: 0,\n errorCount: 0,\n totalMs: 0,\n durations: [],\n lastCalledAt: 0,\n };\n }\n\n private updateToolAccumulator(\n map: Map<string, ToolAccumulator>,\n toolName: string,\n event: ToolCallEvent,\n ): void {\n let acc = map.get(toolName);\n if (!acc) {\n acc = this.newAccumulator();\n map.set(toolName, acc);\n }\n this.updateAccumulator(acc, event);\n }\n\n private updateAccumulator(acc: ToolAccumulator, event: ToolCallEvent): void {\n acc.count++;\n if (!event.success) acc.errorCount++;\n acc.totalMs += event.durationMs;\n acc.durations.push(event.durationMs);\n if (acc.durations.length > this.toolWindowSize) {\n acc.durations.shift();\n }\n acc.lastCalledAt = event.timestamp;\n }\n\n private accToStats(acc: ToolAccumulator): ToolStats {\n const sortedDurations = [...acc.durations].sort((a, b) => a - b);\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n p50Ms: percentile(sortedDurations, 50),\n p95Ms: percentile(sortedDurations, 95),\n p99Ms: percentile(sortedDurations, 99),\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n };\n }\n\n private sessionAccToStats(acc: SessionAccumulator): SessionStats {\n const tools: Record<string, ToolStats> = {};\n for (const [toolName, toolAcc] of acc.tools) {\n tools[toolName] = this.accToStats(toolAcc);\n }\n\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n tools,\n };\n }\n}\n","import type { ToolCallEvent } from \"../types.js\";\n\n/**\n * Console exporter: pretty-prints each batch of events to stdout.\n */\nexport function createConsoleExporter(): (\n events: ToolCallEvent[],\n) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n\n const lines: string[] = [\"[McpAnalytics] Flushing batch:\"];\n\n for (const e of events) {\n const errorSuffix = e.errorCode ? ` (${e.errorCode})` : \"\";\n const status = e.success ? \"OK\" : `ERR${errorSuffix}`;\n const meta = e.sessionId ? ` session=${e.sessionId}` : \"\";\n lines.push(\n ` ${e.toolName} ${status} ${e.durationMs}ms in=${e.inputSize}B out=${e.outputSize}B${meta}`,\n );\n }\n\n console.log(lines.join(\"\\n\"));\n };\n}\n","import type { ExporterFn, ToolCallEvent } from \"../types.js\";\n\n/**\n * Wraps a user-provided export function, catching errors to prevent\n * exporter failures from disrupting the MCP server.\n */\nexport function createCustomExporter(\n fn: ExporterFn,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n try {\n await fn(events);\n } catch (err) {\n console.error(\"[McpAnalytics] Custom exporter error:\", err);\n }\n };\n}\n","import { appendFile } from \"node:fs/promises\";\n\nimport type { JsonConfig, ToolCallEvent } from \"../types.js\";\n\n/**\n * JSON exporter: appends events as JSONL (one JSON object per line) to a file.\n */\nexport function createJsonExporter(\n config: JsonConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n const lines = events.map((e) => JSON.stringify(e)).join(\"\\n\") + \"\\n\";\n await appendFile(config.path, lines, \"utf-8\");\n };\n}\n","import type { OtlpConfig, ToolCallEvent } from \"../types.js\";\nimport type { SpanExporter } from \"@opentelemetry/sdk-trace-base\";\n\n/**\n * OTLP exporter: sends tool call events as OpenTelemetry spans.\n *\n * Uses dynamic imports so that @opentelemetry/* packages are only loaded\n * when this exporter is actually used.\n */\nexport function createOtlpExporter(\n config: OtlpConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n // Lazy-initialized tracer\n let tracerPromise: Promise<OtlpTracer> | undefined;\n\n return async (events) => {\n if (events.length === 0) return;\n\n if (!tracerPromise) {\n tracerPromise = initTracer(config);\n }\n const tracer = await tracerPromise;\n for (const event of events) {\n tracer.exportEvent(event);\n }\n await tracer.flush();\n };\n}\n\ninterface OtlpTracer {\n exportEvent(event: ToolCallEvent): void;\n flush(): Promise<void>;\n}\n\nasync function initTracer(config: OtlpConfig): Promise<OtlpTracer> {\n // Dynamic imports — these only resolve if the user has @opentelemetry installed\n const { SpanStatusCode } = await import(\"@opentelemetry/api\");\n // Create an isolated provider with OTLP HTTP exporter.\n const { BasicTracerProvider, SimpleSpanProcessor } =\n await import(\"@opentelemetry/sdk-trace-base\");\n const { OTLPTraceExporter } =\n await import(\"@opentelemetry/exporter-trace-otlp-http\");\n\n const otlpExporter = new OTLPTraceExporter({\n url: config.endpoint,\n headers: config.headers,\n });\n\n const provider = new BasicTracerProvider({\n spanProcessors: [\n new SimpleSpanProcessor(otlpExporter as unknown as SpanExporter),\n ],\n });\n const tracer = provider.getTracer(\"@mcploom/analytics\");\n\n return {\n exportEvent(event: ToolCallEvent) {\n const span = tracer.startSpan(\"mcp.tool_call\", {\n startTime: new Date(event.timestamp),\n attributes: {\n \"mcp.tool.name\": event.toolName,\n \"mcp.tool.duration_ms\": event.durationMs,\n \"mcp.tool.success\": event.success,\n \"mcp.tool.input_size\": event.inputSize,\n \"mcp.tool.output_size\": event.outputSize,\n ...(event.sessionId && { \"mcp.session.id\": event.sessionId }),\n ...(event.errorMessage && {\n \"mcp.tool.error_message\": event.errorMessage,\n }),\n ...(event.errorCode !== undefined && {\n \"mcp.tool.error_code\": event.errorCode,\n }),\n ...Object.fromEntries(\n Object.entries(event.metadata ?? {}).map(([k, v]) => [\n `mcp.meta.${k}`,\n v,\n ]),\n ),\n },\n });\n\n if (!event.success) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: event.errorMessage,\n });\n }\n\n span.end(new Date(event.timestamp + event.durationMs));\n },\n\n async flush() {\n await otlpExporter?.forceFlush?.();\n },\n };\n}\n","/**\n * OpenTelemetry span helpers for MCP tool call tracing.\n *\n * Uses dynamic import so @opentelemetry/api is only loaded when tracing is enabled.\n * When dd-trace (or any OTel-based APM) is configured as the global tracer provider,\n * spans created here automatically appear as children in the existing trace context.\n */\n\ntype Attribute = string | number | boolean;\n\n// Minimal interface matching the subset of @opentelemetry/api we use.\n// This avoids a hard dependency on the package at the type level.\ninterface OtelSpan {\n setAttribute(key: string, value: Attribute): void;\n setStatus(status: { code: number; message?: string }): void;\n end(): void;\n}\n\ntype OtelContext = Record<string, unknown>;\n\ninterface OtelApi {\n trace: {\n getTracer(name: string): {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, Attribute> },\n ): OtelSpan;\n };\n setSpan(ctx: OtelContext, span: OtelSpan): OtelContext;\n };\n context: {\n active(): OtelContext;\n with<T>(ctx: OtelContext, fn: () => T): T;\n };\n SpanStatusCode: { ERROR: number };\n}\n\nlet otelApi: OtelApi | undefined;\nlet otelLoadFailed = false;\n\n/**\n * Lazily load @opentelemetry/api. Returns undefined if the package is not installed.\n */\nasync function getOtelApi(): Promise<OtelApi | undefined> {\n if (otelApi) return otelApi;\n if (otelLoadFailed) return undefined;\n\n try {\n const api = await import(\"@opentelemetry/api\");\n otelApi = api as unknown as OtelApi;\n return otelApi;\n } catch {\n otelLoadFailed = true;\n return undefined;\n }\n}\n\nexport interface TracingSpan {\n span: OtelSpan;\n context: OtelContext;\n}\n\n/**\n * Start an OpenTelemetry span for a tool call using the global tracer provider.\n * Returns undefined if @opentelemetry/api is not available.\n */\nexport async function startToolSpan(\n toolName: string,\n attributes?: Record<string, Attribute>,\n): Promise<TracingSpan | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const tracer = api.trace.getTracer(\"@mcploom/analytics\");\n const span = tracer.startSpan(\"mcp.tool_call\", {\n attributes: {\n \"mcp.tool.name\": toolName,\n ...attributes,\n },\n });\n\n const spanContext = api.trace.setSpan(api.context.active(), span);\n return { span, context: spanContext };\n}\n\n/**\n * End a tool span, setting error status if the call failed.\n */\nexport function endToolSpan(\n tracing: TracingSpan,\n success: boolean,\n errorMessage?: string,\n): void {\n if (!success && otelApi) {\n tracing.span.setStatus({\n code: otelApi.SpanStatusCode.ERROR,\n message: errorMessage,\n });\n }\n tracing.span.end();\n}\n\n/**\n * Run a function within the context of a span, so downstream OTel-instrumented\n * calls become children of this span.\n */\nexport async function withSpanContext<T>(\n tracing: TracingSpan,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const api = otelApi;\n if (!api) return fn();\n return api.context.with(tracing.context, fn);\n}\n","import type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport type { Collector } from \"./collector.js\";\nimport {\n startToolSpan,\n endToolSpan,\n withSpanContext,\n type TracingSpan,\n} from \"./tracing.js\";\nimport type {\n ToolCallEvent,\n InstrumentedTransport,\n SamplingStrategy,\n} from \"./types.js\";\nimport { byteSize } from \"./utils.js\";\n\n/**\n * Checks if a JSON-RPC message is a request (has `id` and `method`).\n */\nfunction isRequest(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n method: string;\n params?: Record<string, unknown>;\n} {\n return \"id\" in msg && \"method\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is a response with a result.\n */\nfunction isResultResponse(\n msg: JSONRPCMessage,\n): msg is JSONRPCMessage & { id: string | number; result: unknown } {\n return \"id\" in msg && \"result\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is an error response.\n */\nfunction isErrorResponse(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n error: { code: number; message: string };\n} {\n return \"id\" in msg && \"error\" in msg;\n}\n\ninterface PendingCall {\n toolName: string;\n startTime: number;\n inputSize: number;\n tracing?: TracingSpan;\n tracingInit?: Promise<TracingSpan | undefined>;\n}\n\n/**\n * Wraps a Transport to intercept tools/call requests and their responses,\n * recording metrics to the Collector.\n */\nexport function instrumentTransport(\n transport: Transport,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n samplingStrategy: SamplingStrategy = \"per_call\",\n): InstrumentedTransport {\n const pending = new Map<string | number, PendingCall>();\n const sessionSamplingDecisions = new Map<string, boolean>();\n\n // Intercept incoming messages (requests from client)\n const origOnMessage = transport.onmessage;\n const origOnClose = transport.onclose;\n\n const sampleForSession = (sessionKey: string): boolean => {\n if (samplingStrategy !== \"per_session\") {\n // Intentional for performance sampling, not security.\n return Math.random() < sampleRate;\n }\n const cached = sessionSamplingDecisions.get(sessionKey);\n if (cached !== undefined) return cached;\n // Intentional for performance sampling, not security.\n const decision = Math.random() < sampleRate;\n sessionSamplingDecisions.set(sessionKey, decision);\n return decision;\n };\n\n const closePendingCall = (\n call: PendingCall,\n success: boolean,\n errorMessage?: string,\n ) => {\n if (call.tracing) {\n endToolSpan(call.tracing, success, errorMessage);\n return;\n }\n if (call.tracingInit) {\n void call.tracingInit\n .then((span) => {\n if (span) {\n endToolSpan(span, success, errorMessage);\n }\n })\n .catch(() => {\n // ignore tracing init failures\n });\n }\n };\n\n const cleanupPendingCalls = (reason: string) => {\n for (const call of pending.values()) {\n closePendingCall(call, false, reason);\n }\n pending.clear();\n sessionSamplingDecisions.clear();\n };\n\n // We need to intercept onmessage being set (since the server sets it after we wrap)\n // The pattern: wrap the transport so that when server sets onmessage, we inject our interceptor\n const proxy = new Proxy(transport, {\n set(target, prop, value) {\n if (prop === \"onmessage\" && typeof value === \"function\") {\n const userHandler = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onmessage = (\n message: JSONRPCMessage,\n extra?: unknown,\n ) => {\n // Intercept incoming tools/call requests\n if (isRequest(message) && message.method === \"tools/call\") {\n const sessionKey = target.sessionId ?? \"unknown\";\n if (sampleForSession(sessionKey)) {\n const params = message.params as\n | { name?: string; arguments?: unknown }\n | undefined;\n const toolName = params?.name ?? \"unknown\";\n const inputSize = byteSize(params?.arguments);\n const pendingCall: PendingCall = {\n toolName,\n startTime: Date.now(),\n inputSize,\n };\n\n // Start span initialization and keep a promise to avoid race with fast responses.\n if (tracing) {\n pendingCall.tracingInit = startToolSpan(toolName, {\n \"mcp.tool.input_size\": inputSize,\n })\n .then((span) => {\n pendingCall.tracing = span;\n return span;\n })\n .catch(() => undefined);\n }\n\n pending.set(message.id, pendingCall);\n }\n }\n userHandler(message, extra);\n };\n return true;\n }\n if (prop === \"onclose\" && typeof value === \"function\") {\n const userOnClose = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onclose = (...args: unknown[]) => {\n cleanupPendingCalls(\"Transport closed before tool response\");\n userOnClose(...args);\n };\n return true;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any)[prop] = value;\n return true;\n },\n get(target, prop, receiver) {\n if (prop === \"send\") {\n // Intercept outgoing messages (responses from server)\n return async (message: JSONRPCMessage, options?: unknown) => {\n if (\"id\" in message) {\n const id = (message as { id: string | number }).id;\n const call = pending.get(id);\n if (call) {\n pending.delete(id);\n const success = isResultResponse(message);\n const errorMessage = isErrorResponse(message)\n ? (message as { error: { message: string } }).error.message\n : undefined;\n\n let outputPayload: unknown;\n if (isResultResponse(message)) {\n outputPayload = (message as { result: unknown }).result;\n } else if (isErrorResponse(message)) {\n outputPayload = (message as { error: unknown }).error;\n }\n\n const event: ToolCallEvent = {\n toolName: call.toolName,\n sessionId: target.sessionId,\n timestamp: call.startTime,\n durationMs: Date.now() - call.startTime,\n success,\n inputSize: call.inputSize,\n outputSize: byteSize(outputPayload),\n ...(isErrorResponse(message) && {\n errorMessage,\n errorCode: (message as { error: { code: number } }).error\n .code,\n }),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n\n // End the tracing span\n closePendingCall(call, success, errorMessage);\n }\n }\n return (target.send as (...args: unknown[]) => unknown).call(\n target,\n message,\n options,\n );\n };\n }\n if (prop === \"close\") {\n return async (...args: unknown[]) => {\n try {\n return await (\n target.close as (...p: unknown[]) => Promise<unknown>\n )(...args);\n } finally {\n cleanupPendingCalls(\"Transport closed before tool response\");\n }\n };\n }\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return value.bind(target);\n }\n return value;\n },\n });\n\n // If onmessage was already set before we wrapped, re-apply through our proxy\n if (origOnMessage) {\n proxy.onmessage = origOnMessage;\n }\n if (origOnClose) {\n proxy.onclose = origOnClose;\n }\n\n return proxy;\n}\n\n/**\n * Wraps a tool handler function to record metrics.\n * Works with McpServer.tool() callback pattern.\n */\nexport function wrapToolHandler<TArgs extends unknown[], TResult>(\n toolName: string,\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n): (...args: TArgs) => Promise<TResult> {\n return async (...args: TArgs) => {\n const shouldSample = Math.random() < sampleRate;\n if (!shouldSample) {\n return handler(...args);\n }\n\n const startTime = Date.now();\n const inputSize = byteSize(args[0]);\n\n // Start a tracing span if enabled\n const tracingSpan = tracing\n ? await startToolSpan(toolName, { \"mcp.tool.input_size\": inputSize })\n : undefined;\n\n try {\n // Run handler within span context so downstream calls become children\n const result = tracingSpan\n ? await withSpanContext(tracingSpan, () => handler(...args))\n : await handler(...args);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: true,\n inputSize,\n outputSize: byteSize(result),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, true);\n return result;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: false,\n errorMessage,\n inputSize,\n outputSize: 0,\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, false, errorMessage);\n throw err;\n }\n };\n}\n","import type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport { Collector } from \"./collector.js\";\nimport { createConsoleExporter } from \"./exporters/console.js\";\nimport { createCustomExporter } from \"./exporters/custom.js\";\nimport { createJsonExporter } from \"./exporters/json.js\";\nimport { createOtlpExporter } from \"./exporters/otlp.js\";\nimport { instrumentTransport, wrapToolHandler } from \"./middleware.js\";\nimport type {\n AnalyticsConfig,\n AnalyticsSnapshot,\n ExporterFn,\n InstrumentedTransport,\n SessionStats,\n SamplingStrategy,\n ToolStats,\n} from \"./types.js\";\n\n/**\n * MCP Analytics — lightweight observability for MCP servers.\n *\n * @example\n * ```ts\n * const analytics = new McpAnalytics({ exporter: \"console\" });\n *\n * // Instrument a transport\n * const tracked = analytics.instrument(transport);\n * await server.connect(tracked);\n *\n * // Or wrap individual handlers\n * server.tool(\"search\", schema, analytics.track(handler));\n *\n * // Get stats\n * console.log(analytics.getStats());\n *\n * // Shutdown\n * await analytics.flush();\n * ```\n */\nexport class McpAnalytics {\n private readonly collector: Collector;\n private readonly sampleRate: number;\n private readonly metadata?: Record<string, string>;\n private readonly tracing: boolean;\n private readonly samplingStrategy: SamplingStrategy;\n\n constructor(config: AnalyticsConfig) {\n this.sampleRate = config.sampleRate ?? 1;\n this.metadata = config.metadata;\n this.tracing = config.tracing ?? false;\n this.samplingStrategy = config.samplingStrategy ?? \"per_call\";\n\n const exporter = this.resolveExporter(config);\n this.collector = new Collector(\n config.maxBufferSize ?? 10_000,\n exporter,\n config.flushIntervalMs ?? 5_000,\n {\n toolWindowSize: config.toolWindowSize,\n },\n );\n }\n\n /**\n * Instrument an MCP transport to automatically track all tool calls.\n * Returns proxy transport that can be used in place of the original.\n */\n instrument(transport: Transport): InstrumentedTransport {\n return instrumentTransport(\n transport,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n this.samplingStrategy,\n );\n }\n\n /**\n * Wrap a tool handler function to track its execution.\n *\n * @example\n * ```ts\n * server.tool(\"search\", schema, analytics.track(async (params) => {\n * return await doSearch(params);\n * }, \"search\"));\n * ```\n */\n track<TArgs extends unknown[], TResult>(\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n toolName?: string,\n ): (...args: TArgs) => Promise<TResult> {\n const name = toolName ?? (handler.name || \"anonymous\");\n return wrapToolHandler(\n name,\n handler,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n );\n }\n\n /**\n * Get a snapshot of all analytics data.\n */\n getStats(): AnalyticsSnapshot {\n return this.collector.getStats();\n }\n\n /**\n * Get stats for a specific tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n return this.collector.getToolStats(toolName);\n }\n\n /**\n * Get stats for a specific session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n return this.collector.getSessionStats(sessionId);\n }\n\n /**\n * Get top sessions ranked by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n return this.collector.getTopSessions(limit);\n }\n\n /**\n * Flush all pending events to the exporter.\n */\n async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.collector.reset();\n }\n\n /**\n * Stop the analytics instance (clears flush timer and flushes remaining events).\n */\n async shutdown(): Promise<void> {\n await this.collector.destroy();\n }\n\n private resolveExporter(config: AnalyticsConfig): ExporterFn {\n if (typeof config.exporter === \"function\") {\n return createCustomExporter(config.exporter);\n }\n\n switch (config.exporter) {\n case \"console\":\n return createConsoleExporter();\n case \"json\": {\n if (!config.json) {\n throw new Error(\n 'McpAnalytics: \"json\" exporter requires a \"json\" config with \"path\"',\n );\n }\n return createJsonExporter(config.json);\n }\n case \"otlp\": {\n if (!config.otlp) {\n throw new Error(\n 'McpAnalytics: \"otlp\" exporter requires an \"otlp\" config with \"endpoint\"',\n );\n }\n return createOtlpExporter(config.otlp);\n }\n }\n }\n}\n"],"mappings":";;;;;;AAGA,SAAgB,SAAS,OAAwB;AAC/C,KAAI,UAAU,UAAa,UAAU,KAAM,QAAO;CAClD,MAAM,MAAM,OAAO,UAAU,WAAW,QAAQ,KAAK,UAAU,MAAM;AACrE,QAAO,IAAI,aAAa,CAAC,OAAO,IAAI,CAAC;;;;;;AAOvC,SAAgB,WAAW,QAAkB,GAAmB;AAC9D,KAAI,OAAO,WAAW,EAAG,QAAO;AAChC,KAAI,OAAO,WAAW,EAAG,QAAO,OAAO;CAEvC,MAAM,QAAS,IAAI,OAAQ,OAAO,SAAS;CAC3C,MAAM,QAAQ,KAAK,MAAM,MAAM;CAC/B,MAAM,QAAQ,KAAK,KAAK,MAAM;AAE9B,KAAI,UAAU,MAAO,QAAO,OAAO;CAEnC,MAAM,SAAS,QAAQ;AACvB,QAAO,OAAO,UAAU,IAAI,UAAU,OAAO,SAAS;;;;;;;;;ACaxD,IAAa,YAAb,MAAuB;CACrB,AAAiB,SAA0B,EAAE;CAC7C,AAAiB,+BAAe,IAAI,KAA8B;CAClE,AAAiB,sCAAsB,IAAI,KAAiC;CAC5E,AAAQ,aAAa;CACrB,AAAQ,cAAc;CACtB,AAAiB,YAAY,KAAK,KAAK;CAEvC,AAAQ;;CAER,AAAQ,UAA2B,EAAE;CACrC,AAAQ;CACR,AAAiB;CACjB,AAAiB;CAEjB,YACE,AAAiBA,eACjB,AAAiBC,UACjB,iBACA,UAA4B,EAAE,EAC9B;EAJiB;EACA;AAIjB,OAAK,iBAAiB,KAAK,IAAI,GAAG,QAAQ,kBAAkB,KAAM;AAClE,OAAK,eACH,QAAQ,kBACN,UAAU;AACV,WAAQ,MAAM,yCAAyC,MAAM;;AAGjE,MAAI,kBAAkB,GAAG;AACvB,QAAK,aAAa,kBAAkB;AAClC,IAAK,KAAK,OAAO,CAAC,OAAO,UAAU;AACjC,UAAK,aAAa,MAAM;MACxB;MACD,gBAAgB;AAEnB,OACE,KAAK,cACL,OAAO,KAAK,eAAe,YAC3B,WAAW,KAAK,WAEhB,MAAK,WAAW,OAAO;;;;;;CAQ7B,OAAO,OAA4B;AAEjC,MAAI,KAAK,gBAAgB,GAAG;AAC1B,OAAI,KAAK,OAAO,UAAU,KAAK,cAC7B,MAAK,OAAO,OAAO;AAErB,QAAK,OAAO,KAAK,MAAM;;AAEzB,OAAK,QAAQ,KAAK,MAAM;AAExB,OAAK;AACL,MAAI,CAAC,MAAM,QAAS,MAAK;AAGzB,OAAK,sBAAsB,KAAK,cAAc,MAAM,UAAU,MAAM;EAGpE,MAAM,aAAa,MAAM,aAAa;EACtC,IAAI,aAAa,KAAK,oBAAoB,IAAI,WAAW;AACzD,MAAI,CAAC,YAAY;AACf,gBAAa;IAAE,GAAG,KAAK,gBAAgB;IAAE,uBAAO,IAAI,KAAK;IAAE;AAC3D,QAAK,oBAAoB,IAAI,YAAY,WAAW;;AAEtD,OAAK,kBAAkB,YAAY,MAAM;AACzC,OAAK,sBAAsB,WAAW,OAAO,MAAM,UAAU,MAAM;;;;;CAMrE,WAA8B;EAC5B,MAAMC,QAAmC,EAAE;AAC3C,OAAK,MAAM,CAAC,MAAM,QAAQ,KAAK,aAC7B,OAAM,QAAQ,KAAK,WAAW,IAAI;EAGpC,MAAMC,WAAyC,EAAE;AACjD,OAAK,MAAM,CAAC,WAAW,QAAQ,KAAK,oBAClC,UAAS,aAAa,KAAK,kBAAkB,IAAI;AAGnD,SAAO;GACL,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,WAAW,KAAK,aAAa,IAAI,KAAK,cAAc,KAAK,aAAa;GACtE,UAAU,KAAK,KAAK,GAAG,KAAK;GAC5B;GACA;GACD;;;;;CAMH,aAAa,UAAyC;EACpD,MAAM,MAAM,KAAK,aAAa,IAAI,SAAS;AAC3C,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,KAAK,WAAW,IAAI;;;;;CAM7B,gBAAgB,WAA6C;EAC3D,MAAM,MAAM,KAAK,oBAAoB,IAAI,UAAU;AACnD,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,KAAK,kBAAkB,IAAI;;;;;CAMpC,eACE,QAAQ,IAC2C;AACnD,MAAI,SAAS,EAAG,QAAO,EAAE;AACzB,SAAO,CAAC,GAAG,KAAK,oBAAoB,SAAS,CAAC,CAC3C,MAAM,GAAG,MAAM;GACd,MAAM,UAAU,EAAE,GAAG,QAAQ,EAAE,GAAG;AAClC,OAAI,YAAY,EAAG,QAAO;AAC1B,UAAO,EAAE,GAAG,eAAe,EAAE,GAAG;IAChC,CACD,MAAM,GAAG,MAAM,CACf,KAAK,CAAC,WAAW,UAAU;GAC1B;GACA,OAAO,KAAK,kBAAkB,IAAI;GACnC,EAAE;;;;;CAMP,MAAM,QAAuB;AAC3B,MAAI,KAAK,eAAe;AACtB,SAAM,KAAK;AACX;;EAGF,MAAM,MAAM,KAAK,cAAc;AAC/B,OAAK,gBAAgB;AACrB,MAAI;AACF,SAAM;YACE;AACR,OAAI,KAAK,kBAAkB,IACzB,MAAK,gBAAgB;;;;;;CAQ3B,QAAc;AACZ,OAAK,OAAO,SAAS;AACrB,OAAK,QAAQ,SAAS;AACtB,OAAK,aAAa,OAAO;AACzB,OAAK,oBAAoB,OAAO;AAChC,OAAK,aAAa;AAClB,OAAK,cAAc;;;;;CAMrB,MAAM,UAAyB;AAC7B,MAAI,KAAK,YAAY;AACnB,iBAAc,KAAK,WAAW;AAC9B,QAAK,aAAa;;AAEpB,QAAM,KAAK,OAAO;;CAGpB,MAAc,eAA8B;AAC1C,SAAO,KAAK,QAAQ,SAAS,GAAG;GAC9B,MAAM,QAAQ,KAAK;AACnB,QAAK,UAAU,EAAE;AACjB,OAAI;AACF,UAAM,KAAK,SAAS,MAAM;YACnB,OAAO;AAEd,SAAK,UAAU,MAAM,OAAO,KAAK,QAAQ;AACzC,UAAM;;;;CAKZ,AAAQ,iBAAkC;AACxC,SAAO;GACL,OAAO;GACP,YAAY;GACZ,SAAS;GACT,WAAW,EAAE;GACb,cAAc;GACf;;CAGH,AAAQ,sBACN,KACA,UACA,OACM;EACN,IAAI,MAAM,IAAI,IAAI,SAAS;AAC3B,MAAI,CAAC,KAAK;AACR,SAAM,KAAK,gBAAgB;AAC3B,OAAI,IAAI,UAAU,IAAI;;AAExB,OAAK,kBAAkB,KAAK,MAAM;;CAGpC,AAAQ,kBAAkB,KAAsB,OAA4B;AAC1E,MAAI;AACJ,MAAI,CAAC,MAAM,QAAS,KAAI;AACxB,MAAI,WAAW,MAAM;AACrB,MAAI,UAAU,KAAK,MAAM,WAAW;AACpC,MAAI,IAAI,UAAU,SAAS,KAAK,eAC9B,KAAI,UAAU,OAAO;AAEvB,MAAI,eAAe,MAAM;;CAG3B,AAAQ,WAAW,KAAiC;EAClD,MAAM,kBAAkB,CAAC,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,MAAM,IAAI,EAAE;AAChE,SAAO;GACL,OAAO,IAAI;GACX,YAAY,IAAI;GAChB,WAAW,IAAI,QAAQ,IAAI,IAAI,aAAa,IAAI,QAAQ;GACxD,OAAO,WAAW,iBAAiB,GAAG;GACtC,OAAO,WAAW,iBAAiB,GAAG;GACtC,OAAO,WAAW,iBAAiB,GAAG;GACtC,OAAO,IAAI,QAAQ,IAAI,IAAI,UAAU,IAAI,QAAQ;GACjD,cAAc,IAAI;GACnB;;CAGH,AAAQ,kBAAkB,KAAuC;EAC/D,MAAMD,QAAmC,EAAE;AAC3C,OAAK,MAAM,CAAC,UAAU,YAAY,IAAI,MACpC,OAAM,YAAY,KAAK,WAAW,QAAQ;AAG5C,SAAO;GACL,OAAO,IAAI;GACX,YAAY,IAAI;GAChB,WAAW,IAAI,QAAQ,IAAI,IAAI,aAAa,IAAI,QAAQ;GACxD,OAAO,IAAI,QAAQ,IAAI,IAAI,UAAU,IAAI,QAAQ;GACjD,cAAc,IAAI;GAClB;GACD;;;;;;;;;AC/RL,SAAgB,wBAEG;AACjB,QAAO,OAAO,WAAW;AACvB,MAAI,OAAO,WAAW,EAAG;EAEzB,MAAME,QAAkB,CAAC,iCAAiC;AAE1D,OAAK,MAAM,KAAK,QAAQ;GACtB,MAAM,cAAc,EAAE,YAAY,KAAK,EAAE,UAAU,KAAK;GACxD,MAAM,SAAS,EAAE,UAAU,OAAO,MAAM;GACxC,MAAM,OAAO,EAAE,YAAY,YAAY,EAAE,cAAc;AACvD,SAAM,KACJ,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,EAAE,WAAW,QAAQ,EAAE,UAAU,QAAQ,EAAE,WAAW,GAAG,OACvF;;AAGH,UAAQ,IAAI,MAAM,KAAK,KAAK,CAAC;;;;;;;;;;AChBjC,SAAgB,qBACd,IAC4C;AAC5C,QAAO,OAAO,WAAW;AACvB,MAAI;AACF,SAAM,GAAG,OAAO;WACT,KAAK;AACZ,WAAQ,MAAM,yCAAyC,IAAI;;;;;;;;;;ACNjE,SAAgB,mBACd,QAC4C;AAC5C,QAAO,OAAO,WAAW;AACvB,MAAI,OAAO,WAAW,EAAG;EACzB,MAAM,QAAQ,OAAO,KAAK,MAAM,KAAK,UAAU,EAAE,CAAC,CAAC,KAAK,KAAK,GAAG;AAChE,yCAAiB,OAAO,MAAM,OAAO,QAAQ;;;;;;;;;;;;ACJjD,SAAgB,mBACd,QAC4C;CAE5C,IAAIC;AAEJ,QAAO,OAAO,WAAW;AACvB,MAAI,OAAO,WAAW,EAAG;AAEzB,MAAI,CAAC,cACH,iBAAgB,WAAW,OAAO;EAEpC,MAAM,SAAS,MAAM;AACrB,OAAK,MAAM,SAAS,OAClB,QAAO,YAAY,MAAM;AAE3B,QAAM,OAAO,OAAO;;;AASxB,eAAe,WAAW,QAAyC;CAEjE,MAAM,EAAE,mBAAmB,MAAM,OAAO;CAExC,MAAM,EAAE,qBAAqB,wBAC3B,MAAM,OAAO;CACf,MAAM,EAAE,sBACN,MAAM,OAAO;CAEf,MAAM,eAAe,IAAI,kBAAkB;EACzC,KAAK,OAAO;EACZ,SAAS,OAAO;EACjB,CAAC;CAOF,MAAM,SALW,IAAI,oBAAoB,EACvC,gBAAgB,CACd,IAAI,oBAAoB,aAAwC,CACjE,EACF,CAAC,CACsB,UAAU,qBAAqB;AAEvD,QAAO;EACL,YAAY,OAAsB;GAChC,MAAM,OAAO,OAAO,UAAU,iBAAiB;IAC7C,WAAW,IAAI,KAAK,MAAM,UAAU;IACpC,YAAY;KACV,iBAAiB,MAAM;KACvB,wBAAwB,MAAM;KAC9B,oBAAoB,MAAM;KAC1B,uBAAuB,MAAM;KAC7B,wBAAwB,MAAM;KAC9B,GAAI,MAAM,aAAa,EAAE,kBAAkB,MAAM,WAAW;KAC5D,GAAI,MAAM,gBAAgB,EACxB,0BAA0B,MAAM,cACjC;KACD,GAAI,MAAM,cAAc,UAAa,EACnC,uBAAuB,MAAM,WAC9B;KACD,GAAG,OAAO,YACR,OAAO,QAAQ,MAAM,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,OAAO,CACnD,YAAY,KACZ,EACD,CAAC,CACH;KACF;IACF,CAAC;AAEF,OAAI,CAAC,MAAM,QACT,MAAK,UAAU;IACb,MAAM,eAAe;IACrB,SAAS,MAAM;IAChB,CAAC;AAGJ,QAAK,IAAI,IAAI,KAAK,MAAM,YAAY,MAAM,WAAW,CAAC;;EAGxD,MAAM,QAAQ;AACZ,SAAM,cAAc,cAAc;;EAErC;;;;;ACzDH,IAAIC;AACJ,IAAI,iBAAiB;;;;AAKrB,eAAe,aAA2C;AACxD,KAAI,QAAS,QAAO;AACpB,KAAI,eAAgB,QAAO;AAE3B,KAAI;AAEF,YADY,MAAM,OAAO;AAEzB,SAAO;SACD;AACN,mBAAiB;AACjB;;;;;;;AAaJ,eAAsB,cACpB,UACA,YACkC;CAClC,MAAM,MAAM,MAAM,YAAY;AAC9B,KAAI,CAAC,IAAK,QAAO;CAGjB,MAAM,OADS,IAAI,MAAM,UAAU,qBAAqB,CACpC,UAAU,iBAAiB,EAC7C,YAAY;EACV,iBAAiB;EACjB,GAAG;EACJ,EACF,CAAC;AAGF,QAAO;EAAE;EAAM,SADK,IAAI,MAAM,QAAQ,IAAI,QAAQ,QAAQ,EAAE,KAAK;EAC5B;;;;;AAMvC,SAAgB,YACd,SACA,SACA,cACM;AACN,KAAI,CAAC,WAAW,QACd,SAAQ,KAAK,UAAU;EACrB,MAAM,QAAQ,eAAe;EAC7B,SAAS;EACV,CAAC;AAEJ,SAAQ,KAAK,KAAK;;;;;;AAOpB,eAAsB,gBACpB,SACA,IACY;CACZ,MAAM,MAAM;AACZ,KAAI,CAAC,IAAK,QAAO,IAAI;AACrB,QAAO,IAAI,QAAQ,KAAK,QAAQ,SAAS,GAAG;;;;;;;;AC5F9C,SAAS,UAAU,KAIjB;AACA,QAAO,QAAQ,OAAO,YAAY;;;;;AAMpC,SAAS,iBACP,KACkE;AAClE,QAAO,QAAQ,OAAO,YAAY;;;;;AAMpC,SAAS,gBAAgB,KAGvB;AACA,QAAO,QAAQ,OAAO,WAAW;;;;;;AAenC,SAAgB,oBACd,WACA,WACA,YACA,gBACA,SACA,mBAAqC,YACd;CACvB,MAAM,0BAAU,IAAI,KAAmC;CACvD,MAAM,2CAA2B,IAAI,KAAsB;CAG3D,MAAM,gBAAgB,UAAU;CAChC,MAAM,cAAc,UAAU;CAE9B,MAAM,oBAAoB,eAAgC;AACxD,MAAI,qBAAqB,cAEvB,QAAO,KAAK,QAAQ,GAAG;EAEzB,MAAM,SAAS,yBAAyB,IAAI,WAAW;AACvD,MAAI,WAAW,OAAW,QAAO;EAEjC,MAAM,WAAW,KAAK,QAAQ,GAAG;AACjC,2BAAyB,IAAI,YAAY,SAAS;AAClD,SAAO;;CAGT,MAAM,oBACJ,MACA,SACA,iBACG;AACH,MAAI,KAAK,SAAS;AAChB,eAAY,KAAK,SAAS,SAAS,aAAa;AAChD;;AAEF,MAAI,KAAK,YACP,CAAK,KAAK,YACP,MAAM,SAAS;AACd,OAAI,KACF,aAAY,MAAM,SAAS,aAAa;IAE1C,CACD,YAAY,GAEX;;CAIR,MAAM,uBAAuB,WAAmB;AAC9C,OAAK,MAAM,QAAQ,QAAQ,QAAQ,CACjC,kBAAiB,MAAM,OAAO,OAAO;AAEvC,UAAQ,OAAO;AACf,2BAAyB,OAAO;;CAKlC,MAAM,QAAQ,IAAI,MAAM,WAAW;EACjC,IAAI,QAAQ,MAAM,OAAO;AACvB,OAAI,SAAS,eAAe,OAAO,UAAU,YAAY;IACvD,MAAM,cAAc;AAEpB,IAAC,OAAe,aACd,SACA,UACG;AAEH,SAAI,UAAU,QAAQ,IAAI,QAAQ,WAAW,cAE3C;UAAI,iBADe,OAAO,aAAa,UACP,EAAE;OAChC,MAAM,SAAS,QAAQ;OAGvB,MAAM,WAAW,QAAQ,QAAQ;OACjC,MAAM,YAAY,SAAS,QAAQ,UAAU;OAC7C,MAAMC,cAA2B;QAC/B;QACA,WAAW,KAAK,KAAK;QACrB;QACD;AAGD,WAAI,QACF,aAAY,cAAc,cAAc,UAAU,EAChD,uBAAuB,WACxB,CAAC,CACC,MAAM,SAAS;AACd,oBAAY,UAAU;AACtB,eAAO;SACP,CACD,YAAY,OAAU;AAG3B,eAAQ,IAAI,QAAQ,IAAI,YAAY;;;AAGxC,iBAAY,SAAS,MAAM;;AAE7B,WAAO;;AAET,OAAI,SAAS,aAAa,OAAO,UAAU,YAAY;IACrD,MAAM,cAAc;AAEpB,IAAC,OAAe,WAAW,GAAG,SAAoB;AAChD,yBAAoB,wCAAwC;AAC5D,iBAAY,GAAG,KAAK;;AAEtB,WAAO;;AAGT,GAAC,OAAe,QAAQ;AACxB,UAAO;;EAET,IAAI,QAAQ,MAAM,UAAU;AAC1B,OAAI,SAAS,OAEX,QAAO,OAAO,SAAyB,YAAsB;AAC3D,QAAI,QAAQ,SAAS;KACnB,MAAM,KAAM,QAAoC;KAChD,MAAM,OAAO,QAAQ,IAAI,GAAG;AAC5B,SAAI,MAAM;AACR,cAAQ,OAAO,GAAG;MAClB,MAAM,UAAU,iBAAiB,QAAQ;MACzC,MAAM,eAAe,gBAAgB,QAAQ,GACxC,QAA2C,MAAM,UAClD;MAEJ,IAAIC;AACJ,UAAI,iBAAiB,QAAQ,CAC3B,iBAAiB,QAAgC;eACxC,gBAAgB,QAAQ,CACjC,iBAAiB,QAA+B;MAGlD,MAAMC,QAAuB;OAC3B,UAAU,KAAK;OACf,WAAW,OAAO;OAClB,WAAW,KAAK;OAChB,YAAY,KAAK,KAAK,GAAG,KAAK;OAC9B;OACA,WAAW,KAAK;OAChB,YAAY,SAAS,cAAc;OACnC,GAAI,gBAAgB,QAAQ,IAAI;QAC9B;QACA,WAAY,QAAwC,MACjD;QACJ;OACD,GAAI,kBAAkB,EAAE,UAAU,gBAAgB;OACnD;AACD,gBAAU,OAAO,MAAM;AAGvB,uBAAiB,MAAM,SAAS,aAAa;;;AAGjD,WAAQ,OAAO,KAAyC,KACtD,QACA,SACA,QACD;;AAGL,OAAI,SAAS,QACX,QAAO,OAAO,GAAG,SAAoB;AACnC,QAAI;AACF,YAAO,MACL,OAAO,MACP,GAAG,KAAK;cACF;AACR,yBAAoB,wCAAwC;;;GAIlE,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS;AACjD,OAAI,OAAO,UAAU,WACnB,QAAO,MAAM,KAAK,OAAO;AAE3B,UAAO;;EAEV,CAAC;AAGF,KAAI,cACF,OAAM,YAAY;AAEpB,KAAI,YACF,OAAM,UAAU;AAGlB,QAAO;;;;;;AAOT,SAAgB,gBACd,UACA,SACA,WACA,YACA,gBACA,SACsC;AACtC,QAAO,OAAO,GAAG,SAAgB;AAE/B,MAAI,EADiB,KAAK,QAAQ,GAAG,YAEnC,QAAO,QAAQ,GAAG,KAAK;EAGzB,MAAM,YAAY,KAAK,KAAK;EAC5B,MAAM,YAAY,SAAS,KAAK,GAAG;EAGnC,MAAM,cAAc,UAChB,MAAM,cAAc,UAAU,EAAE,uBAAuB,WAAW,CAAC,GACnE;AAEJ,MAAI;GAEF,MAAM,SAAS,cACX,MAAM,gBAAgB,mBAAmB,QAAQ,GAAG,KAAK,CAAC,GAC1D,MAAM,QAAQ,GAAG,KAAK;GAC1B,MAAMA,QAAuB;IAC3B;IACA,WAAW;IACX,YAAY,KAAK,KAAK,GAAG;IACzB,SAAS;IACT;IACA,YAAY,SAAS,OAAO;IAC5B,GAAI,kBAAkB,EAAE,UAAU,gBAAgB;IACnD;AACD,aAAU,OAAO,MAAM;AACvB,OAAI,YAAa,aAAY,aAAa,KAAK;AAC/C,UAAO;WACA,KAAK;GACZ,MAAM,eAAe,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI;GACrE,MAAMA,QAAuB;IAC3B;IACA,WAAW;IACX,YAAY,KAAK,KAAK,GAAG;IACzB,SAAS;IACT;IACA;IACA,YAAY;IACZ,GAAI,kBAAkB,EAAE,UAAU,gBAAgB;IACnD;AACD,aAAU,OAAO,MAAM;AACvB,OAAI,YAAa,aAAY,aAAa,OAAO,aAAa;AAC9D,SAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;AChRZ,IAAa,eAAb,MAA0B;CACxB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YAAY,QAAyB;AACnC,OAAK,aAAa,OAAO,cAAc;AACvC,OAAK,WAAW,OAAO;AACvB,OAAK,UAAU,OAAO,WAAW;AACjC,OAAK,mBAAmB,OAAO,oBAAoB;EAEnD,MAAM,WAAW,KAAK,gBAAgB,OAAO;AAC7C,OAAK,YAAY,IAAI,UACnB,OAAO,iBAAiB,KACxB,UACA,OAAO,mBAAmB,KAC1B,EACE,gBAAgB,OAAO,gBACxB,CACF;;;;;;CAOH,WAAW,WAA6C;AACtD,SAAO,oBACL,WACA,KAAK,WACL,KAAK,YACL,KAAK,UACL,KAAK,SACL,KAAK,iBACN;;;;;;;;;;;;CAaH,MACE,SACA,UACsC;AAEtC,SAAO,gBADM,aAAa,QAAQ,QAAQ,cAGxC,SACA,KAAK,WACL,KAAK,YACL,KAAK,UACL,KAAK,QACN;;;;;CAMH,WAA8B;AAC5B,SAAO,KAAK,UAAU,UAAU;;;;;CAMlC,aAAa,UAAyC;AACpD,SAAO,KAAK,UAAU,aAAa,SAAS;;;;;CAM9C,gBAAgB,WAA6C;AAC3D,SAAO,KAAK,UAAU,gBAAgB,UAAU;;;;;CAMlD,eACE,QAAQ,IAC2C;AACnD,SAAO,KAAK,UAAU,eAAe,MAAM;;;;;CAM7C,MAAM,QAAuB;AAC3B,QAAM,KAAK,UAAU,OAAO;;;;;CAM9B,QAAc;AACZ,OAAK,UAAU,OAAO;;;;;CAMxB,MAAM,WAA0B;AAC9B,QAAM,KAAK,UAAU,SAAS;;CAGhC,AAAQ,gBAAgB,QAAqC;AAC3D,MAAI,OAAO,OAAO,aAAa,WAC7B,QAAO,qBAAqB,OAAO,SAAS;AAG9C,UAAQ,OAAO,UAAf;GACE,KAAK,UACH,QAAO,uBAAuB;GAChC,KAAK;AACH,QAAI,CAAC,OAAO,KACV,OAAM,IAAI,MACR,2EACD;AAEH,WAAO,mBAAmB,OAAO,KAAK;GAExC,KAAK;AACH,QAAI,CAAC,OAAO,KACV,OAAM,IAAI,MACR,gFACD;AAEH,WAAO,mBAAmB,OAAO,KAAK"}
1
+ {"version":3,"file":"index.cjs","names":["maxBufferSize: number","exporter: ExporterFn","tools: Record<string, ToolStats>","sessions: Record<string, SessionStats>","lines: string[]","tracerPromise: Promise<OtlpTracer> | undefined","otelApi: OtelApi | undefined","pendingCall: PendingCall","outputPayload: unknown","event: ToolCallEvent"],"sources":["../src/utils.ts","../src/collector.ts","../src/exporters/console.ts","../src/exporters/custom.ts","../src/exporters/json.ts","../src/exporters/otlp.ts","../src/tracing.ts","../src/middleware.ts","../src/analytics.ts"],"sourcesContent":["/**\n * Returns the byte length of a string (UTF-8).\n */\nexport function byteSize(value: unknown): number {\n if (value === undefined || value === null) return 0;\n const str = typeof value === \"string\" ? value : JSON.stringify(value);\n return new TextEncoder().encode(str).byteLength;\n}\n\n/**\n * Compute a percentile from a sorted array of numbers.\n * Uses linear interpolation between the closest ranks.\n */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n if (sorted.length === 1) return sorted[0];\n\n const index = (p / 100) * (sorted.length - 1);\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n\n if (lower === upper) return sorted[lower];\n\n const weight = index - lower;\n return sorted[lower] * (1 - weight) + sorted[upper] * weight;\n}\n\n/**\n * Insert a value into an already-sorted array (ascending), maintaining sort order.\n */\nexport function sortedInsert(arr: number[], value: number): void {\n let lo = 0;\n let hi = arr.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (arr[mid] < value) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n arr.splice(lo, 0, value);\n}\n","import type {\n AnalyticsSnapshot,\n ExporterFn,\n SessionStats,\n ToolCallEvent,\n ToolStats,\n} from \"./types.js\";\nimport { percentile } from \"./utils.js\";\n\n/**\n * Per-tool accumulator for computing stats without scanning the full buffer.\n */\ninterface ToolAccumulator {\n count: number;\n errorCount: number;\n totalMs: number;\n /** Sorted durations for percentile computation */\n durations: number[];\n lastCalledAt: number;\n}\n\n/**\n * Per-session accumulator.\n */\ninterface SessionAccumulator extends ToolAccumulator {\n tools: Map<string, ToolAccumulator>;\n}\n\ninterface CollectorOptions {\n toolWindowSize?: number;\n onFlushError?: (error: unknown) => void;\n}\n\n/**\n * In-memory ring buffer that collects ToolCallEvents, computes stats,\n * and periodically flushes to an exporter.\n */\nexport class Collector {\n private readonly buffer: ToolCallEvent[] = [];\n private readonly accumulators = new Map<string, ToolAccumulator>();\n private readonly sessionAccumulators = new Map<string, SessionAccumulator>();\n private totalCalls = 0;\n private totalErrors = 0;\n private readonly startTime = Date.now();\n\n private flushTimer: ReturnType<typeof setInterval> | undefined;\n /** Events accumulated since last flush, to be sent to the exporter */\n private pending: ToolCallEvent[] = [];\n private flushInFlight: Promise<void> | undefined;\n private readonly toolWindowSize: number;\n private readonly onFlushError: (error: unknown) => void;\n\n /**\n * Creates an event collector with bounded buffering and optional periodic flushing.\n */\n constructor(\n private readonly maxBufferSize: number,\n private readonly exporter: ExporterFn,\n flushIntervalMs: number,\n options: CollectorOptions = {},\n ) {\n this.toolWindowSize = Math.max(1, options.toolWindowSize ?? 2_048);\n this.onFlushError =\n options.onFlushError ??\n ((error) => {\n console.error(\"[McpAnalytics] Exporter flush failed:\", error);\n });\n\n if (flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush().catch((error) => {\n this.onFlushError(error);\n });\n }, flushIntervalMs);\n // Don't hold the process open for analytics flushing\n if (\n this.flushTimer &&\n typeof this.flushTimer === \"object\" &&\n \"unref\" in this.flushTimer\n ) {\n this.flushTimer.unref();\n }\n }\n }\n\n /**\n * Record a new tool call event.\n */\n record(event: ToolCallEvent): void {\n // Ring buffer: drop oldest when full\n if (this.maxBufferSize > 0) {\n if (this.buffer.length >= this.maxBufferSize) {\n this.buffer.shift();\n }\n this.buffer.push(event);\n }\n this.pending.push(event);\n\n this.totalCalls++;\n if (!event.success) this.totalErrors++;\n\n // Update per-tool accumulator\n this.updateToolAccumulator(this.accumulators, event.toolName, event);\n\n // Update per-session accumulator\n const sessionKey = event.sessionId ?? \"unknown\";\n let sessionAcc = this.sessionAccumulators.get(sessionKey);\n if (!sessionAcc) {\n sessionAcc = { ...this.newAccumulator(), tools: new Map() };\n this.sessionAccumulators.set(sessionKey, sessionAcc);\n }\n this.updateAccumulator(sessionAcc, event);\n this.updateToolAccumulator(sessionAcc.tools, event.toolName, event);\n }\n\n /**\n * Get aggregated stats for all tools.\n */\n getStats(): AnalyticsSnapshot {\n const tools: Record<string, ToolStats> = {};\n for (const [name, acc] of this.accumulators) {\n tools[name] = this.accToStats(acc);\n }\n\n const sessions: Record<string, SessionStats> = {};\n for (const [sessionId, acc] of this.sessionAccumulators) {\n sessions[sessionId] = this.sessionAccToStats(acc);\n }\n\n return {\n totalCalls: this.totalCalls,\n totalErrors: this.totalErrors,\n errorRate: this.totalCalls > 0 ? this.totalErrors / this.totalCalls : 0,\n uptimeMs: Date.now() - this.startTime,\n tools,\n sessions,\n };\n }\n\n /**\n * Get stats for a single tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n const acc = this.accumulators.get(toolName);\n if (!acc) return undefined;\n return this.accToStats(acc);\n }\n\n /**\n * Get stats for a single session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n const acc = this.sessionAccumulators.get(sessionId);\n if (!acc) return undefined;\n return this.sessionAccToStats(acc);\n }\n\n /**\n * Get top sessions ordered by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n if (limit <= 0) return [];\n return [...this.sessionAccumulators.entries()]\n .sort((a, b) => {\n const byCount = b[1].count - a[1].count;\n if (byCount !== 0) return byCount;\n return b[1].lastCalledAt - a[1].lastCalledAt;\n })\n .slice(0, limit)\n .map(([sessionId, acc]) => ({\n sessionId,\n stats: this.sessionAccToStats(acc),\n }));\n }\n\n /**\n * Flush pending events to the exporter.\n */\n async flush(): Promise<void> {\n if (this.flushInFlight) {\n await this.flushInFlight;\n return;\n }\n\n const run = this.flushPending();\n this.flushInFlight = run;\n try {\n await run;\n } finally {\n if (this.flushInFlight === run) {\n this.flushInFlight = undefined;\n }\n }\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.buffer.length = 0;\n this.pending.length = 0;\n this.accumulators.clear();\n this.sessionAccumulators.clear();\n this.totalCalls = 0;\n this.totalErrors = 0;\n }\n\n /**\n * Stop the flush timer and flush remaining events.\n */\n async destroy(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = undefined;\n }\n await this.flush();\n }\n\n private async flushPending(): Promise<void> {\n while (this.pending.length > 0) {\n const batch = this.pending;\n this.pending = [];\n try {\n await this.exporter(batch);\n } catch (error) {\n // Re-queue batch to avoid data loss on transient exporter failures.\n this.pending = batch.concat(this.pending);\n throw error;\n }\n }\n }\n\n private newAccumulator(): ToolAccumulator {\n return {\n count: 0,\n errorCount: 0,\n totalMs: 0,\n durations: [],\n lastCalledAt: 0,\n };\n }\n\n private updateToolAccumulator(\n map: Map<string, ToolAccumulator>,\n toolName: string,\n event: ToolCallEvent,\n ): void {\n let acc = map.get(toolName);\n if (!acc) {\n acc = this.newAccumulator();\n map.set(toolName, acc);\n }\n this.updateAccumulator(acc, event);\n }\n\n private updateAccumulator(acc: ToolAccumulator, event: ToolCallEvent): void {\n acc.count++;\n if (!event.success) acc.errorCount++;\n acc.totalMs += event.durationMs;\n acc.durations.push(event.durationMs);\n if (acc.durations.length > this.toolWindowSize) {\n acc.durations.shift();\n }\n acc.lastCalledAt = event.timestamp;\n }\n\n private accToStats(acc: ToolAccumulator): ToolStats {\n const sortedDurations = [...acc.durations].sort((a, b) => a - b);\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n p50Ms: percentile(sortedDurations, 50),\n p95Ms: percentile(sortedDurations, 95),\n p99Ms: percentile(sortedDurations, 99),\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n };\n }\n\n private sessionAccToStats(acc: SessionAccumulator): SessionStats {\n const tools: Record<string, ToolStats> = {};\n for (const [toolName, toolAcc] of acc.tools) {\n tools[toolName] = this.accToStats(toolAcc);\n }\n\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n tools,\n };\n }\n}\n","import type { ToolCallEvent } from \"../types.js\";\n\n/**\n * Console exporter: pretty-prints each batch of events to stdout.\n */\nexport function createConsoleExporter(): (\n events: ToolCallEvent[],\n) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n\n const lines: string[] = [\"[McpAnalytics] Flushing batch:\"];\n\n for (const e of events) {\n const errorSuffix = e.errorCode ? ` (${e.errorCode})` : \"\";\n const status = e.success ? \"OK\" : `ERR${errorSuffix}`;\n const meta = e.sessionId ? ` session=${e.sessionId}` : \"\";\n lines.push(\n ` ${e.toolName} ${status} ${e.durationMs}ms in=${e.inputSize}B out=${e.outputSize}B${meta}`,\n );\n }\n\n console.log(lines.join(\"\\n\"));\n };\n}\n","import type { ExporterFn, ToolCallEvent } from \"../types.js\";\n\n/**\n * Wraps a user-provided export function, catching errors to prevent\n * exporter failures from disrupting the MCP server.\n */\nexport function createCustomExporter(\n fn: ExporterFn,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n try {\n await fn(events);\n } catch (err) {\n console.error(\"[McpAnalytics] Custom exporter error:\", err);\n }\n };\n}\n","import { appendFile } from \"node:fs/promises\";\n\nimport type { JsonConfig, ToolCallEvent } from \"../types.js\";\n\n/**\n * JSON exporter: appends events as JSONL (one JSON object per line) to a file.\n */\nexport function createJsonExporter(\n config: JsonConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n const lines = events.map((e) => JSON.stringify(e)).join(\"\\n\") + \"\\n\";\n await appendFile(config.path, lines, \"utf-8\");\n };\n}\n","import type { OtlpConfig, ToolCallEvent } from \"../types.js\";\nimport type { SpanExporter } from \"@opentelemetry/sdk-trace-base\";\n\n/**\n * OTLP exporter: sends tool call events as OpenTelemetry spans.\n *\n * Uses dynamic imports so that @opentelemetry/* packages are only loaded\n * when this exporter is actually used.\n */\nexport function createOtlpExporter(\n config: OtlpConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n // Lazy-initialized tracer\n let tracerPromise: Promise<OtlpTracer> | undefined;\n\n return async (events) => {\n if (events.length === 0) return;\n\n if (!tracerPromise) {\n tracerPromise = initTracer(config);\n }\n const tracer = await tracerPromise;\n for (const event of events) {\n tracer.exportEvent(event);\n }\n await tracer.flush();\n };\n}\n\ninterface OtlpTracer {\n exportEvent(event: ToolCallEvent): void;\n flush(): Promise<void>;\n}\n\nasync function initTracer(config: OtlpConfig): Promise<OtlpTracer> {\n // Dynamic imports — these only resolve if the user has @opentelemetry installed\n const { SpanStatusCode } = await import(\"@opentelemetry/api\");\n // Create an isolated provider with OTLP HTTP exporter.\n const { BasicTracerProvider, SimpleSpanProcessor } =\n await import(\"@opentelemetry/sdk-trace-base\");\n const { OTLPTraceExporter } =\n await import(\"@opentelemetry/exporter-trace-otlp-http\");\n\n const otlpExporter = new OTLPTraceExporter({\n url: config.endpoint,\n headers: config.headers,\n });\n\n const provider = new BasicTracerProvider({\n spanProcessors: [\n new SimpleSpanProcessor(otlpExporter as unknown as SpanExporter),\n ],\n });\n const tracer = provider.getTracer(\"@mcploom/analytics\");\n\n return {\n exportEvent(event: ToolCallEvent) {\n const span = tracer.startSpan(\"mcp.tool_call\", {\n startTime: new Date(event.timestamp),\n attributes: {\n \"mcp.tool.name\": event.toolName,\n \"mcp.tool.duration_ms\": event.durationMs,\n \"mcp.tool.success\": event.success,\n \"mcp.tool.input_size\": event.inputSize,\n \"mcp.tool.output_size\": event.outputSize,\n ...(event.sessionId && { \"mcp.session.id\": event.sessionId }),\n ...(event.errorMessage && {\n \"mcp.tool.error_message\": event.errorMessage,\n }),\n ...(event.errorCode !== undefined && {\n \"mcp.tool.error_code\": event.errorCode,\n }),\n ...Object.fromEntries(\n Object.entries(event.metadata ?? {}).map(([k, v]) => [\n `mcp.meta.${k}`,\n v,\n ]),\n ),\n },\n });\n\n if (!event.success) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: event.errorMessage,\n });\n }\n\n span.end(new Date(event.timestamp + event.durationMs));\n },\n\n async flush() {\n await otlpExporter?.forceFlush?.();\n },\n };\n}\n","/**\n * OpenTelemetry span helpers for MCP tool call tracing.\n *\n * Uses dynamic import so @opentelemetry/api is only loaded when tracing is enabled.\n * When dd-trace (or any OTel-based APM) is configured as the global tracer provider,\n * spans created here automatically appear as children in the existing trace context.\n */\n\ntype Attribute = string | number | boolean;\n\n// Minimal interface matching the subset of @opentelemetry/api we use.\n// This avoids a hard dependency on the package at the type level.\ninterface OtelSpan {\n setAttribute(key: string, value: Attribute): void;\n setStatus(status: { code: number; message?: string }): void;\n end(): void;\n}\n\ntype OtelContext = Record<string, unknown>;\n\ninterface OtelApi {\n trace: {\n getTracer(name: string): {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, Attribute> },\n ): OtelSpan;\n };\n setSpan(ctx: OtelContext, span: OtelSpan): OtelContext;\n };\n context: {\n active(): OtelContext;\n with<T>(ctx: OtelContext, fn: () => T): T;\n };\n SpanStatusCode: { ERROR: number };\n}\n\nlet otelApi: OtelApi | undefined;\nlet otelLoadFailed = false;\n\n/**\n * Lazily load @opentelemetry/api. Returns undefined if the package is not installed.\n */\nasync function getOtelApi(): Promise<OtelApi | undefined> {\n if (otelApi) return otelApi;\n if (otelLoadFailed) return undefined;\n\n try {\n const api = await import(\"@opentelemetry/api\");\n otelApi = api as unknown as OtelApi;\n return otelApi;\n } catch {\n otelLoadFailed = true;\n return undefined;\n }\n}\n\n/**\n * Span handle and context pair returned when analytics tracing starts a tool span.\n */\nexport interface TracingSpan {\n span: OtelSpan;\n context: OtelContext;\n}\n\n/**\n * Start an OpenTelemetry span for a tool call using the global tracer provider.\n * Returns undefined if @opentelemetry/api is not available.\n */\nexport async function startToolSpan(\n toolName: string,\n attributes?: Record<string, Attribute>,\n): Promise<TracingSpan | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const tracer = api.trace.getTracer(\"@mcploom/analytics\");\n const span = tracer.startSpan(\"mcp.tool_call\", {\n attributes: {\n \"mcp.tool.name\": toolName,\n ...attributes,\n },\n });\n\n const spanContext = api.trace.setSpan(api.context.active(), span);\n return { span, context: spanContext };\n}\n\n/**\n * End a tool span, setting error status if the call failed.\n */\nexport function endToolSpan(\n tracing: TracingSpan,\n success: boolean,\n errorMessage?: string,\n): void {\n if (!success && otelApi) {\n tracing.span.setStatus({\n code: otelApi.SpanStatusCode.ERROR,\n message: errorMessage,\n });\n }\n tracing.span.end();\n}\n\n/**\n * Run a function within the context of a span, so downstream OTel-instrumented\n * calls become children of this span.\n */\nexport async function withSpanContext<T>(\n tracing: TracingSpan,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const api = otelApi;\n if (!api) return fn();\n return api.context.with(tracing.context, fn);\n}\n","import type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport type { Collector } from \"./collector.js\";\nimport {\n startToolSpan,\n endToolSpan,\n withSpanContext,\n type TracingSpan,\n} from \"./tracing.js\";\nimport type {\n ToolCallEvent,\n InstrumentedTransport,\n SamplingStrategy,\n} from \"./types.js\";\nimport { byteSize } from \"./utils.js\";\n\n/**\n * Checks if a JSON-RPC message is a request (has `id` and `method`).\n */\nfunction isRequest(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n method: string;\n params?: Record<string, unknown>;\n} {\n return \"id\" in msg && \"method\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is a response with a result.\n */\nfunction isResultResponse(\n msg: JSONRPCMessage,\n): msg is JSONRPCMessage & { id: string | number; result: unknown } {\n return \"id\" in msg && \"result\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is an error response.\n */\nfunction isErrorResponse(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n error: { code: number; message: string };\n} {\n return \"id\" in msg && \"error\" in msg;\n}\n\ninterface PendingCall {\n toolName: string;\n startTime: number;\n inputSize: number;\n tracing?: TracingSpan;\n tracingInit?: Promise<TracingSpan | undefined>;\n}\n\n/**\n * Wraps a Transport to intercept tools/call requests and their responses,\n * recording metrics to the Collector.\n */\nexport function instrumentTransport(\n transport: Transport,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n samplingStrategy: SamplingStrategy = \"per_call\",\n): InstrumentedTransport {\n const pending = new Map<string | number, PendingCall>();\n const sessionSamplingDecisions = new Map<string, boolean>();\n\n // Intercept incoming messages (requests from client)\n const origOnMessage = transport.onmessage;\n const origOnClose = transport.onclose;\n\n const sampleForSession = (sessionKey: string): boolean => {\n if (samplingStrategy !== \"per_session\") {\n // Intentional for performance sampling, not security.\n return Math.random() < sampleRate;\n }\n const cached = sessionSamplingDecisions.get(sessionKey);\n if (cached !== undefined) return cached;\n // Intentional for performance sampling, not security.\n const decision = Math.random() < sampleRate;\n sessionSamplingDecisions.set(sessionKey, decision);\n return decision;\n };\n\n const closePendingCall = (\n call: PendingCall,\n success: boolean,\n errorMessage?: string,\n ) => {\n if (call.tracing) {\n endToolSpan(call.tracing, success, errorMessage);\n return;\n }\n if (call.tracingInit) {\n void call.tracingInit\n .then((span) => {\n if (span) {\n endToolSpan(span, success, errorMessage);\n }\n })\n .catch(() => {\n // ignore tracing init failures\n });\n }\n };\n\n const cleanupPendingCalls = (reason: string) => {\n for (const call of pending.values()) {\n closePendingCall(call, false, reason);\n }\n pending.clear();\n sessionSamplingDecisions.clear();\n };\n\n // We need to intercept onmessage being set (since the server sets it after we wrap)\n // The pattern: wrap the transport so that when server sets onmessage, we inject our interceptor\n const proxy = new Proxy(transport, {\n set(target, prop, value) {\n if (prop === \"onmessage\" && typeof value === \"function\") {\n const userHandler = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onmessage = (\n message: JSONRPCMessage,\n extra?: unknown,\n ) => {\n // Intercept incoming tools/call requests\n if (isRequest(message) && message.method === \"tools/call\") {\n const sessionKey = target.sessionId ?? \"unknown\";\n if (sampleForSession(sessionKey)) {\n const params = message.params as\n | { name?: string; arguments?: unknown }\n | undefined;\n const toolName = params?.name ?? \"unknown\";\n const inputSize = byteSize(params?.arguments);\n const pendingCall: PendingCall = {\n toolName,\n startTime: Date.now(),\n inputSize,\n };\n\n // Start span initialization and keep a promise to avoid race with fast responses.\n if (tracing) {\n pendingCall.tracingInit = startToolSpan(toolName, {\n \"mcp.tool.input_size\": inputSize,\n })\n .then((span) => {\n pendingCall.tracing = span;\n return span;\n })\n .catch(() => undefined);\n }\n\n pending.set(message.id, pendingCall);\n }\n }\n userHandler(message, extra);\n };\n return true;\n }\n if (prop === \"onclose\" && typeof value === \"function\") {\n const userOnClose = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onclose = (...args: unknown[]) => {\n cleanupPendingCalls(\"Transport closed before tool response\");\n userOnClose(...args);\n };\n return true;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any)[prop] = value;\n return true;\n },\n get(target, prop, receiver) {\n if (prop === \"send\") {\n // Intercept outgoing messages (responses from server)\n return async (message: JSONRPCMessage, options?: unknown) => {\n if (\"id\" in message) {\n const id = (message as { id: string | number }).id;\n const call = pending.get(id);\n if (call) {\n pending.delete(id);\n const success = isResultResponse(message);\n const errorMessage = isErrorResponse(message)\n ? (message as { error: { message: string } }).error.message\n : undefined;\n\n let outputPayload: unknown;\n if (isResultResponse(message)) {\n outputPayload = (message as { result: unknown }).result;\n } else if (isErrorResponse(message)) {\n outputPayload = (message as { error: unknown }).error;\n }\n\n const event: ToolCallEvent = {\n toolName: call.toolName,\n sessionId: target.sessionId,\n timestamp: call.startTime,\n durationMs: Date.now() - call.startTime,\n success,\n inputSize: call.inputSize,\n outputSize: byteSize(outputPayload),\n ...(isErrorResponse(message) && {\n errorMessage,\n errorCode: (message as { error: { code: number } }).error\n .code,\n }),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n\n // End the tracing span\n closePendingCall(call, success, errorMessage);\n }\n }\n return (target.send as (...args: unknown[]) => unknown).call(\n target,\n message,\n options,\n );\n };\n }\n if (prop === \"close\") {\n return async (...args: unknown[]) => {\n try {\n return await (\n target.close as (...p: unknown[]) => Promise<unknown>\n )(...args);\n } finally {\n cleanupPendingCalls(\"Transport closed before tool response\");\n }\n };\n }\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return value.bind(target);\n }\n return value;\n },\n });\n\n // If onmessage was already set before we wrapped, re-apply through our proxy\n if (origOnMessage) {\n proxy.onmessage = origOnMessage;\n }\n if (origOnClose) {\n proxy.onclose = origOnClose;\n }\n\n return proxy;\n}\n\n/**\n * Wraps a tool handler function to record metrics.\n * Works with McpServer.tool() callback pattern.\n */\nexport function wrapToolHandler<TArgs extends unknown[], TResult>(\n toolName: string,\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n): (...args: TArgs) => Promise<TResult> {\n return async (...args: TArgs) => {\n const shouldSample = Math.random() < sampleRate;\n if (!shouldSample) {\n return handler(...args);\n }\n\n const startTime = Date.now();\n const inputSize = byteSize(args[0]);\n\n // Start a tracing span if enabled\n const tracingSpan = tracing\n ? await startToolSpan(toolName, { \"mcp.tool.input_size\": inputSize })\n : undefined;\n\n try {\n // Run handler within span context so downstream calls become children\n const result = tracingSpan\n ? await withSpanContext(tracingSpan, () => handler(...args))\n : await handler(...args);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: true,\n inputSize,\n outputSize: byteSize(result),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, true);\n return result;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: false,\n errorMessage,\n inputSize,\n outputSize: 0,\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, false, errorMessage);\n throw err;\n }\n };\n}\n","import type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport { Collector } from \"./collector.js\";\nimport { createConsoleExporter } from \"./exporters/console.js\";\nimport { createCustomExporter } from \"./exporters/custom.js\";\nimport { createJsonExporter } from \"./exporters/json.js\";\nimport { createOtlpExporter } from \"./exporters/otlp.js\";\nimport { instrumentTransport, wrapToolHandler } from \"./middleware.js\";\nimport type {\n AnalyticsConfig,\n AnalyticsSnapshot,\n ExporterFn,\n InstrumentedTransport,\n SessionStats,\n SamplingStrategy,\n ToolStats,\n} from \"./types.js\";\n\n/**\n * MCP Analytics — lightweight observability for MCP servers.\n *\n * @example\n * ```ts\n * const analytics = new McpAnalytics({ exporter: \"console\" });\n *\n * // Instrument a transport\n * const tracked = analytics.instrument(transport);\n * await server.connect(tracked);\n *\n * // Or wrap individual handlers\n * server.tool(\"search\", schema, analytics.track(handler));\n *\n * // Get stats\n * console.log(analytics.getStats());\n *\n * // Shutdown\n * await analytics.flush();\n * ```\n */\nexport class McpAnalytics {\n private readonly collector: Collector;\n private readonly sampleRate: number;\n private readonly metadata?: Record<string, string>;\n private readonly tracing: boolean;\n private readonly samplingStrategy: SamplingStrategy;\n\n /**\n * Creates an analytics instance with the configured exporter, buffering, and sampling behavior.\n */\n constructor(config: AnalyticsConfig) {\n this.sampleRate = config.sampleRate ?? 1;\n this.metadata = config.metadata;\n this.tracing = config.tracing ?? false;\n this.samplingStrategy = config.samplingStrategy ?? \"per_call\";\n\n const exporter = this.resolveExporter(config);\n this.collector = new Collector(\n config.maxBufferSize ?? 10_000,\n exporter,\n config.flushIntervalMs ?? 5_000,\n {\n toolWindowSize: config.toolWindowSize,\n },\n );\n }\n\n /**\n * Instrument an MCP transport to automatically track all tool calls.\n * Returns proxy transport that can be used in place of the original.\n */\n instrument(transport: Transport): InstrumentedTransport {\n return instrumentTransport(\n transport,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n this.samplingStrategy,\n );\n }\n\n /**\n * Wrap a tool handler function to track its execution.\n *\n * @example\n * ```ts\n * server.tool(\"search\", schema, analytics.track(async (params) => {\n * return await doSearch(params);\n * }, \"search\"));\n * ```\n */\n track<TArgs extends unknown[], TResult>(\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n toolName?: string,\n ): (...args: TArgs) => Promise<TResult> {\n const name = toolName ?? (handler.name || \"anonymous\");\n return wrapToolHandler(\n name,\n handler,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n );\n }\n\n /**\n * Get a snapshot of all analytics data.\n */\n getStats(): AnalyticsSnapshot {\n return this.collector.getStats();\n }\n\n /**\n * Get stats for a specific tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n return this.collector.getToolStats(toolName);\n }\n\n /**\n * Get stats for a specific session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n return this.collector.getSessionStats(sessionId);\n }\n\n /**\n * Get top sessions ranked by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n return this.collector.getTopSessions(limit);\n }\n\n /**\n * Flush all pending events to the exporter.\n */\n async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.collector.reset();\n }\n\n /**\n * Stop the analytics instance (clears flush timer and flushes remaining events).\n */\n async shutdown(): Promise<void> {\n await this.collector.destroy();\n }\n\n private resolveExporter(config: AnalyticsConfig): ExporterFn {\n if (typeof config.exporter === \"function\") {\n return createCustomExporter(config.exporter);\n }\n\n switch (config.exporter) {\n case \"console\":\n return createConsoleExporter();\n case \"json\": {\n if (!config.json) {\n throw new Error(\n 'McpAnalytics: \"json\" exporter requires a \"json\" config with \"path\"',\n );\n }\n return createJsonExporter(config.json);\n }\n case \"otlp\": {\n if (!config.otlp) {\n throw new Error(\n 'McpAnalytics: \"otlp\" exporter requires an \"otlp\" config with \"endpoint\"',\n );\n }\n return createOtlpExporter(config.otlp);\n }\n }\n }\n}\n"],"mappings":"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"}
package/dist/index.d.cts CHANGED
@@ -144,6 +144,9 @@ declare class McpAnalytics {
144
144
  private readonly metadata?;
145
145
  private readonly tracing;
146
146
  private readonly samplingStrategy;
147
+ /**
148
+ * Creates an analytics instance with the configured exporter, buffering, and sampling behavior.
149
+ */
147
150
  constructor(config: AnalyticsConfig);
148
151
  /**
149
152
  * Instrument an MCP transport to automatically track all tool calls.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/analytics.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AA0BiB,UA1BA,aAAA,CA0BS;EAcT;EAKO,QAAA,EAAA,MAAA;EAAf;EACkB,SAAA,CAAA,EAAA,MAAA;EAAf;EAAM,SAAA,EAAA,MAAA;EAMD;EAYL,UAAA,EAAA,MAAU;EAKL;EAQA,OAAA,EAAA,OAAU;EAOV;EAEyB,YAAA,CAAA,EAAA,MAAA;EAEjC;EAEA,SAAA,CAAA,EAAA,MAAA;EAQI;EAEQ,SAAA,EAAA,MAAA;EAAgB;EAYzB,UAAA,EAAA,MAAA;EAMA;aAlGC;;;ACcb;;AA4BwB,UDpCP,SAAA,CCoCO;EAAY,KAAA,EAAA,MAAA;EAsBb,UAAA,EAAA,MAAA;EAAU,SAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,KAAA,EAAA,MAAA;EAE5B,KAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,YAAA,EAAA,MAAA;;;;;AAsCpB,UDpFY,iBAAA,CCoFZ;EAOY,UAAA,EAAA,MAAA;EAcG,WAAA,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;;SDpGlB,eAAe;YACZ,eAAe;;;;;UAMV,YAAA;;;;;;SAMR,eAAe;;;;;KAMZ,UAAA,YAAsB,oBAAoB;;;;UAKrC,UAAA;;YAEL;;;;;UAMK,UAAA;;;;;;UAOA,eAAA;;0CAEyB;;SAEjC;;SAEA;;;;;;;;aAQI;;qBAEQ;;;;;;;;;;;KAYT,gBAAA;;;;;KAMA,qBAAA,GAAwB;;;;AAtHpC;AA0BA;AAcA;;;;;;AAYA;AAYA;AAKA;AAQA;AAOA;;;;;;;AA4BA;AAMY,cCpFC,YAAA,CDoFoB;;;;ECpFpB,iBAAY,OAAA;EAOH,iBAAA,gBAAA;EAqBE,WAAA,CAAA,MAAA,EArBF,eAqBE;EAAY;;;;EAsBO,UAAA,CAAA,SAAA,EAtBnB,SAsBmB,CAAA,EAtBP,qBAsBO;EAE5B;;;;;;;;;;EA2DY,KAAA,CAAA,cAAA,OAAA,EAAA,EAAA,OAAA,CAAA,CAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EA7DJ,KA6DI,EAAA,GA7DM,OA6DN,GA7DgB,OA6DhB,CA7DwB,OA6DxB,CAAA,EAAA,QAAA,CAAA,EAAA,MAAA,CAAA,EAAA,CAAA,GAAA,IAAA,EA3DZ,KA2DY,EAAA,GA3DF,OA2DE,CA3DM,OA2DN,CAAA;;;;cA5Cb;;;;kCAOoB;;;;sCAOI;;;;kCASjC;;WAAkC;;;;;WAOtB;;;;;;;;cAcG"}
1
+ {"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/analytics.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AA0BiB,UA1BA,aAAA,CA0BS;EAcT;EAKO,QAAA,EAAA,MAAA;EAAf;EACkB,SAAA,CAAA,EAAA,MAAA;EAAf;EAAM,SAAA,EAAA,MAAA;EAMD;EAYL,UAAA,EAAA,MAAU;EAKL;EAQA,OAAA,EAAA,OAAU;EAOV;EAEyB,YAAA,CAAA,EAAA,MAAA;EAEjC;EAEA,SAAA,CAAA,EAAA,MAAA;EAQI;EAEQ,SAAA,EAAA,MAAA;EAAgB;EAYzB,UAAA,EAAA,MAAA;EAMA;aAlGC;;;ACcb;;AA+BwB,UDvCP,SAAA,CCuCO;EAAY,KAAA,EAAA,MAAA;EAsBb,UAAA,EAAA,MAAA;EAAU,SAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,KAAA,EAAA,MAAA;EAE5B,KAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,YAAA,EAAA,MAAA;;;;;AAsCpB,UDvFY,iBAAA,CCuFZ;EAOY,UAAA,EAAA,MAAA;EAcG,WAAA,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;;SDvGlB,eAAe;YACZ,eAAe;;;;;UAMV,YAAA;;;;;;SAMR,eAAe;;;;;KAMZ,UAAA,YAAsB,oBAAoB;;;;UAKrC,UAAA;;YAEL;;;;;UAMK,UAAA;;;;;;UAOA,eAAA;;0CAEyB;;SAEjC;;SAEA;;;;;;;;aAQI;;qBAEQ;;;;;;;;;;;KAYT,gBAAA;;;;;KAMA,qBAAA,GAAwB;;;;AAtHpC;AA0BA;AAcA;;;;;;AAYA;AAYA;AAKA;AAQA;AAOA;;;;;;;AA4BA;AAMY,cCpFC,YAAA,CDoFoB;;;;ECpFpB,iBAAY,OAAA;EAUH,iBAAA,gBAAA;EAqBE;;;EAsBS,WAAA,CAAA,MAAA,EA3CX,eA2CW;EAAkB;;;;EAE1B,UAAA,CAAA,SAAA,EAxBD,SAwBC,CAAA,EAxBW,qBAwBX;EAeX;;;;;;;;;;6DAjBS,UAAU,UAAU,QAAQ,wCAEpC,UAAU,QAAQ;;;;cAenB;;;;kCAOoB;;;;sCAOI;;;;kCASjC;;WAAkC;;;;;WAOtB;;;;;;;;cAcG"}
package/dist/index.d.ts CHANGED
@@ -144,6 +144,9 @@ declare class McpAnalytics {
144
144
  private readonly metadata?;
145
145
  private readonly tracing;
146
146
  private readonly samplingStrategy;
147
+ /**
148
+ * Creates an analytics instance with the configured exporter, buffering, and sampling behavior.
149
+ */
147
150
  constructor(config: AnalyticsConfig);
148
151
  /**
149
152
  * Instrument an MCP transport to automatically track all tool calls.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/analytics.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AA0BiB,UA1BA,aAAA,CA0BS;EAcT;EAKO,QAAA,EAAA,MAAA;EAAf;EACkB,SAAA,CAAA,EAAA,MAAA;EAAf;EAAM,SAAA,EAAA,MAAA;EAMD;EAYL,UAAA,EAAA,MAAU;EAKL;EAQA,OAAA,EAAA,OAAU;EAOV;EAEyB,YAAA,CAAA,EAAA,MAAA;EAEjC;EAEA,SAAA,CAAA,EAAA,MAAA;EAQI;EAEQ,SAAA,EAAA,MAAA;EAAgB;EAYzB,UAAA,EAAA,MAAA;EAMA;aAlGC;;;ACcb;;AA4BwB,UDpCP,SAAA,CCoCO;EAAY,KAAA,EAAA,MAAA;EAsBb,UAAA,EAAA,MAAA;EAAU,SAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,KAAA,EAAA,MAAA;EAE5B,KAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,YAAA,EAAA,MAAA;;;;;AAsCpB,UDpFY,iBAAA,CCoFZ;EAOY,UAAA,EAAA,MAAA;EAcG,WAAA,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;;SDpGlB,eAAe;YACZ,eAAe;;;;;UAMV,YAAA;;;;;;SAMR,eAAe;;;;;KAMZ,UAAA,YAAsB,oBAAoB;;;;UAKrC,UAAA;;YAEL;;;;;UAMK,UAAA;;;;;;UAOA,eAAA;;0CAEyB;;SAEjC;;SAEA;;;;;;;;aAQI;;qBAEQ;;;;;;;;;;;KAYT,gBAAA;;;;;KAMA,qBAAA,GAAwB;;;;AAtHpC;AA0BA;AAcA;;;;;;AAYA;AAYA;AAKA;AAQA;AAOA;;;;;;;AA4BA;AAMY,cCpFC,YAAA,CDoFoB;;;;ECpFpB,iBAAY,OAAA;EAOH,iBAAA,gBAAA;EAqBE,WAAA,CAAA,MAAA,EArBF,eAqBE;EAAY;;;;EAsBO,UAAA,CAAA,SAAA,EAtBnB,SAsBmB,CAAA,EAtBP,qBAsBO;EAE5B;;;;;;;;;;EA2DY,KAAA,CAAA,cAAA,OAAA,EAAA,EAAA,OAAA,CAAA,CAAA,OAAA,EAAA,CAAA,GAAA,IAAA,EA7DJ,KA6DI,EAAA,GA7DM,OA6DN,GA7DgB,OA6DhB,CA7DwB,OA6DxB,CAAA,EAAA,QAAA,CAAA,EAAA,MAAA,CAAA,EAAA,CAAA,GAAA,IAAA,EA3DZ,KA2DY,EAAA,GA3DF,OA2DE,CA3DM,OA2DN,CAAA;;;;cA5Cb;;;;kCAOoB;;;;sCAOI;;;;kCASjC;;WAAkC;;;;;WAOtB;;;;;;;;cAcG"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/types.ts","../src/analytics.ts"],"sourcesContent":[],"mappings":";;;;;;AAKA;AA0BiB,UA1BA,aAAA,CA0BS;EAcT;EAKO,QAAA,EAAA,MAAA;EAAf;EACkB,SAAA,CAAA,EAAA,MAAA;EAAf;EAAM,SAAA,EAAA,MAAA;EAMD;EAYL,UAAA,EAAA,MAAU;EAKL;EAQA,OAAA,EAAA,OAAU;EAOV;EAEyB,YAAA,CAAA,EAAA,MAAA;EAEjC;EAEA,SAAA,CAAA,EAAA,MAAA;EAQI;EAEQ,SAAA,EAAA,MAAA;EAAgB;EAYzB,UAAA,EAAA,MAAA;EAMA;aAlGC;;;ACcb;;AA+BwB,UDvCP,SAAA,CCuCO;EAAY,KAAA,EAAA,MAAA;EAsBb,UAAA,EAAA,MAAA;EAAU,SAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,KAAA,EAAA,MAAA;EAE5B,KAAA,EAAA,MAAA;EAAkB,KAAA,EAAA,MAAA;EAAR,YAAA,EAAA,MAAA;;;;;AAsCpB,UDvFY,iBAAA,CCuFZ;EAOY,UAAA,EAAA,MAAA;EAcG,WAAA,EAAA,MAAA;EAAO,SAAA,EAAA,MAAA;;SDvGlB,eAAe;YACZ,eAAe;;;;;UAMV,YAAA;;;;;;SAMR,eAAe;;;;;KAMZ,UAAA,YAAsB,oBAAoB;;;;UAKrC,UAAA;;YAEL;;;;;UAMK,UAAA;;;;;;UAOA,eAAA;;0CAEyB;;SAEjC;;SAEA;;;;;;;;aAQI;;qBAEQ;;;;;;;;;;;KAYT,gBAAA;;;;;KAMA,qBAAA,GAAwB;;;;AAtHpC;AA0BA;AAcA;;;;;;AAYA;AAYA;AAKA;AAQA;AAOA;;;;;;;AA4BA;AAMY,cCpFC,YAAA,CDoFoB;;;;ECpFpB,iBAAY,OAAA;EAUH,iBAAA,gBAAA;EAqBE;;;EAsBS,WAAA,CAAA,MAAA,EA3CX,eA2CW;EAAkB;;;;EAE1B,UAAA,CAAA,SAAA,EAxBD,SAwBC,CAAA,EAxBW,qBAwBX;EAeX;;;;;;;;;;6DAjBS,UAAU,UAAU,QAAQ,wCAEpC,UAAU,QAAQ;;;;cAenB;;;;kCAOoB;;;;sCAOI;;;;kCASjC;;WAAkC;;;;;WAOtB;;;;;;;;cAcG"}
package/dist/index.js CHANGED
@@ -43,6 +43,9 @@ var Collector = class {
43
43
  flushInFlight;
44
44
  toolWindowSize;
45
45
  onFlushError;
46
+ /**
47
+ * Creates an event collector with bounded buffering and optional periodic flushing.
48
+ */
46
49
  constructor(maxBufferSize, exporter, flushIntervalMs, options = {}) {
47
50
  this.maxBufferSize = maxBufferSize;
48
51
  this.exporter = exporter;
@@ -596,6 +599,9 @@ var McpAnalytics = class {
596
599
  metadata;
597
600
  tracing;
598
601
  samplingStrategy;
602
+ /**
603
+ * Creates an analytics instance with the configured exporter, buffering, and sampling behavior.
604
+ */
599
605
  constructor(config) {
600
606
  this.sampleRate = config.sampleRate ?? 1;
601
607
  this.metadata = config.metadata;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["maxBufferSize: number","exporter: ExporterFn","tools: Record<string, ToolStats>","sessions: Record<string, SessionStats>","lines: string[]","tracerPromise: Promise<OtlpTracer> | undefined","otelApi: OtelApi | undefined","pendingCall: PendingCall","outputPayload: unknown","event: ToolCallEvent"],"sources":["../src/utils.ts","../src/collector.ts","../src/exporters/console.ts","../src/exporters/custom.ts","../src/exporters/json.ts","../src/exporters/otlp.ts","../src/tracing.ts","../src/middleware.ts","../src/analytics.ts"],"sourcesContent":["/**\n * Returns the byte length of a string (UTF-8).\n */\nexport function byteSize(value: unknown): number {\n if (value === undefined || value === null) return 0;\n const str = typeof value === \"string\" ? value : JSON.stringify(value);\n return new TextEncoder().encode(str).byteLength;\n}\n\n/**\n * Compute a percentile from a sorted array of numbers.\n * Uses linear interpolation between the closest ranks.\n */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n if (sorted.length === 1) return sorted[0];\n\n const index = (p / 100) * (sorted.length - 1);\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n\n if (lower === upper) return sorted[lower];\n\n const weight = index - lower;\n return sorted[lower] * (1 - weight) + sorted[upper] * weight;\n}\n\n/**\n * Insert a value into an already-sorted array (ascending), maintaining sort order.\n */\nexport function sortedInsert(arr: number[], value: number): void {\n let lo = 0;\n let hi = arr.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (arr[mid] < value) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n arr.splice(lo, 0, value);\n}\n","import type {\n AnalyticsSnapshot,\n ExporterFn,\n SessionStats,\n ToolCallEvent,\n ToolStats,\n} from \"./types.js\";\nimport { percentile } from \"./utils.js\";\n\n/**\n * Per-tool accumulator for computing stats without scanning the full buffer.\n */\ninterface ToolAccumulator {\n count: number;\n errorCount: number;\n totalMs: number;\n /** Sorted durations for percentile computation */\n durations: number[];\n lastCalledAt: number;\n}\n\n/**\n * Per-session accumulator.\n */\ninterface SessionAccumulator extends ToolAccumulator {\n tools: Map<string, ToolAccumulator>;\n}\n\ninterface CollectorOptions {\n toolWindowSize?: number;\n onFlushError?: (error: unknown) => void;\n}\n\n/**\n * In-memory ring buffer that collects ToolCallEvents, computes stats,\n * and periodically flushes to an exporter.\n */\nexport class Collector {\n private readonly buffer: ToolCallEvent[] = [];\n private readonly accumulators = new Map<string, ToolAccumulator>();\n private readonly sessionAccumulators = new Map<string, SessionAccumulator>();\n private totalCalls = 0;\n private totalErrors = 0;\n private readonly startTime = Date.now();\n\n private flushTimer: ReturnType<typeof setInterval> | undefined;\n /** Events accumulated since last flush, to be sent to the exporter */\n private pending: ToolCallEvent[] = [];\n private flushInFlight: Promise<void> | undefined;\n private readonly toolWindowSize: number;\n private readonly onFlushError: (error: unknown) => void;\n\n constructor(\n private readonly maxBufferSize: number,\n private readonly exporter: ExporterFn,\n flushIntervalMs: number,\n options: CollectorOptions = {},\n ) {\n this.toolWindowSize = Math.max(1, options.toolWindowSize ?? 2_048);\n this.onFlushError =\n options.onFlushError ??\n ((error) => {\n console.error(\"[McpAnalytics] Exporter flush failed:\", error);\n });\n\n if (flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush().catch((error) => {\n this.onFlushError(error);\n });\n }, flushIntervalMs);\n // Don't hold the process open for analytics flushing\n if (\n this.flushTimer &&\n typeof this.flushTimer === \"object\" &&\n \"unref\" in this.flushTimer\n ) {\n this.flushTimer.unref();\n }\n }\n }\n\n /**\n * Record a new tool call event.\n */\n record(event: ToolCallEvent): void {\n // Ring buffer: drop oldest when full\n if (this.maxBufferSize > 0) {\n if (this.buffer.length >= this.maxBufferSize) {\n this.buffer.shift();\n }\n this.buffer.push(event);\n }\n this.pending.push(event);\n\n this.totalCalls++;\n if (!event.success) this.totalErrors++;\n\n // Update per-tool accumulator\n this.updateToolAccumulator(this.accumulators, event.toolName, event);\n\n // Update per-session accumulator\n const sessionKey = event.sessionId ?? \"unknown\";\n let sessionAcc = this.sessionAccumulators.get(sessionKey);\n if (!sessionAcc) {\n sessionAcc = { ...this.newAccumulator(), tools: new Map() };\n this.sessionAccumulators.set(sessionKey, sessionAcc);\n }\n this.updateAccumulator(sessionAcc, event);\n this.updateToolAccumulator(sessionAcc.tools, event.toolName, event);\n }\n\n /**\n * Get aggregated stats for all tools.\n */\n getStats(): AnalyticsSnapshot {\n const tools: Record<string, ToolStats> = {};\n for (const [name, acc] of this.accumulators) {\n tools[name] = this.accToStats(acc);\n }\n\n const sessions: Record<string, SessionStats> = {};\n for (const [sessionId, acc] of this.sessionAccumulators) {\n sessions[sessionId] = this.sessionAccToStats(acc);\n }\n\n return {\n totalCalls: this.totalCalls,\n totalErrors: this.totalErrors,\n errorRate: this.totalCalls > 0 ? this.totalErrors / this.totalCalls : 0,\n uptimeMs: Date.now() - this.startTime,\n tools,\n sessions,\n };\n }\n\n /**\n * Get stats for a single tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n const acc = this.accumulators.get(toolName);\n if (!acc) return undefined;\n return this.accToStats(acc);\n }\n\n /**\n * Get stats for a single session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n const acc = this.sessionAccumulators.get(sessionId);\n if (!acc) return undefined;\n return this.sessionAccToStats(acc);\n }\n\n /**\n * Get top sessions ordered by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n if (limit <= 0) return [];\n return [...this.sessionAccumulators.entries()]\n .sort((a, b) => {\n const byCount = b[1].count - a[1].count;\n if (byCount !== 0) return byCount;\n return b[1].lastCalledAt - a[1].lastCalledAt;\n })\n .slice(0, limit)\n .map(([sessionId, acc]) => ({\n sessionId,\n stats: this.sessionAccToStats(acc),\n }));\n }\n\n /**\n * Flush pending events to the exporter.\n */\n async flush(): Promise<void> {\n if (this.flushInFlight) {\n await this.flushInFlight;\n return;\n }\n\n const run = this.flushPending();\n this.flushInFlight = run;\n try {\n await run;\n } finally {\n if (this.flushInFlight === run) {\n this.flushInFlight = undefined;\n }\n }\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.buffer.length = 0;\n this.pending.length = 0;\n this.accumulators.clear();\n this.sessionAccumulators.clear();\n this.totalCalls = 0;\n this.totalErrors = 0;\n }\n\n /**\n * Stop the flush timer and flush remaining events.\n */\n async destroy(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = undefined;\n }\n await this.flush();\n }\n\n private async flushPending(): Promise<void> {\n while (this.pending.length > 0) {\n const batch = this.pending;\n this.pending = [];\n try {\n await this.exporter(batch);\n } catch (error) {\n // Re-queue batch to avoid data loss on transient exporter failures.\n this.pending = batch.concat(this.pending);\n throw error;\n }\n }\n }\n\n private newAccumulator(): ToolAccumulator {\n return {\n count: 0,\n errorCount: 0,\n totalMs: 0,\n durations: [],\n lastCalledAt: 0,\n };\n }\n\n private updateToolAccumulator(\n map: Map<string, ToolAccumulator>,\n toolName: string,\n event: ToolCallEvent,\n ): void {\n let acc = map.get(toolName);\n if (!acc) {\n acc = this.newAccumulator();\n map.set(toolName, acc);\n }\n this.updateAccumulator(acc, event);\n }\n\n private updateAccumulator(acc: ToolAccumulator, event: ToolCallEvent): void {\n acc.count++;\n if (!event.success) acc.errorCount++;\n acc.totalMs += event.durationMs;\n acc.durations.push(event.durationMs);\n if (acc.durations.length > this.toolWindowSize) {\n acc.durations.shift();\n }\n acc.lastCalledAt = event.timestamp;\n }\n\n private accToStats(acc: ToolAccumulator): ToolStats {\n const sortedDurations = [...acc.durations].sort((a, b) => a - b);\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n p50Ms: percentile(sortedDurations, 50),\n p95Ms: percentile(sortedDurations, 95),\n p99Ms: percentile(sortedDurations, 99),\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n };\n }\n\n private sessionAccToStats(acc: SessionAccumulator): SessionStats {\n const tools: Record<string, ToolStats> = {};\n for (const [toolName, toolAcc] of acc.tools) {\n tools[toolName] = this.accToStats(toolAcc);\n }\n\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n tools,\n };\n }\n}\n","import type { ToolCallEvent } from \"../types.js\";\n\n/**\n * Console exporter: pretty-prints each batch of events to stdout.\n */\nexport function createConsoleExporter(): (\n events: ToolCallEvent[],\n) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n\n const lines: string[] = [\"[McpAnalytics] Flushing batch:\"];\n\n for (const e of events) {\n const errorSuffix = e.errorCode ? ` (${e.errorCode})` : \"\";\n const status = e.success ? \"OK\" : `ERR${errorSuffix}`;\n const meta = e.sessionId ? ` session=${e.sessionId}` : \"\";\n lines.push(\n ` ${e.toolName} ${status} ${e.durationMs}ms in=${e.inputSize}B out=${e.outputSize}B${meta}`,\n );\n }\n\n console.log(lines.join(\"\\n\"));\n };\n}\n","import type { ExporterFn, ToolCallEvent } from \"../types.js\";\n\n/**\n * Wraps a user-provided export function, catching errors to prevent\n * exporter failures from disrupting the MCP server.\n */\nexport function createCustomExporter(\n fn: ExporterFn,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n try {\n await fn(events);\n } catch (err) {\n console.error(\"[McpAnalytics] Custom exporter error:\", err);\n }\n };\n}\n","import { appendFile } from \"node:fs/promises\";\n\nimport type { JsonConfig, ToolCallEvent } from \"../types.js\";\n\n/**\n * JSON exporter: appends events as JSONL (one JSON object per line) to a file.\n */\nexport function createJsonExporter(\n config: JsonConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n const lines = events.map((e) => JSON.stringify(e)).join(\"\\n\") + \"\\n\";\n await appendFile(config.path, lines, \"utf-8\");\n };\n}\n","import type { OtlpConfig, ToolCallEvent } from \"../types.js\";\nimport type { SpanExporter } from \"@opentelemetry/sdk-trace-base\";\n\n/**\n * OTLP exporter: sends tool call events as OpenTelemetry spans.\n *\n * Uses dynamic imports so that @opentelemetry/* packages are only loaded\n * when this exporter is actually used.\n */\nexport function createOtlpExporter(\n config: OtlpConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n // Lazy-initialized tracer\n let tracerPromise: Promise<OtlpTracer> | undefined;\n\n return async (events) => {\n if (events.length === 0) return;\n\n if (!tracerPromise) {\n tracerPromise = initTracer(config);\n }\n const tracer = await tracerPromise;\n for (const event of events) {\n tracer.exportEvent(event);\n }\n await tracer.flush();\n };\n}\n\ninterface OtlpTracer {\n exportEvent(event: ToolCallEvent): void;\n flush(): Promise<void>;\n}\n\nasync function initTracer(config: OtlpConfig): Promise<OtlpTracer> {\n // Dynamic imports — these only resolve if the user has @opentelemetry installed\n const { SpanStatusCode } = await import(\"@opentelemetry/api\");\n // Create an isolated provider with OTLP HTTP exporter.\n const { BasicTracerProvider, SimpleSpanProcessor } =\n await import(\"@opentelemetry/sdk-trace-base\");\n const { OTLPTraceExporter } =\n await import(\"@opentelemetry/exporter-trace-otlp-http\");\n\n const otlpExporter = new OTLPTraceExporter({\n url: config.endpoint,\n headers: config.headers,\n });\n\n const provider = new BasicTracerProvider({\n spanProcessors: [\n new SimpleSpanProcessor(otlpExporter as unknown as SpanExporter),\n ],\n });\n const tracer = provider.getTracer(\"@mcploom/analytics\");\n\n return {\n exportEvent(event: ToolCallEvent) {\n const span = tracer.startSpan(\"mcp.tool_call\", {\n startTime: new Date(event.timestamp),\n attributes: {\n \"mcp.tool.name\": event.toolName,\n \"mcp.tool.duration_ms\": event.durationMs,\n \"mcp.tool.success\": event.success,\n \"mcp.tool.input_size\": event.inputSize,\n \"mcp.tool.output_size\": event.outputSize,\n ...(event.sessionId && { \"mcp.session.id\": event.sessionId }),\n ...(event.errorMessage && {\n \"mcp.tool.error_message\": event.errorMessage,\n }),\n ...(event.errorCode !== undefined && {\n \"mcp.tool.error_code\": event.errorCode,\n }),\n ...Object.fromEntries(\n Object.entries(event.metadata ?? {}).map(([k, v]) => [\n `mcp.meta.${k}`,\n v,\n ]),\n ),\n },\n });\n\n if (!event.success) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: event.errorMessage,\n });\n }\n\n span.end(new Date(event.timestamp + event.durationMs));\n },\n\n async flush() {\n await otlpExporter?.forceFlush?.();\n },\n };\n}\n","/**\n * OpenTelemetry span helpers for MCP tool call tracing.\n *\n * Uses dynamic import so @opentelemetry/api is only loaded when tracing is enabled.\n * When dd-trace (or any OTel-based APM) is configured as the global tracer provider,\n * spans created here automatically appear as children in the existing trace context.\n */\n\ntype Attribute = string | number | boolean;\n\n// Minimal interface matching the subset of @opentelemetry/api we use.\n// This avoids a hard dependency on the package at the type level.\ninterface OtelSpan {\n setAttribute(key: string, value: Attribute): void;\n setStatus(status: { code: number; message?: string }): void;\n end(): void;\n}\n\ntype OtelContext = Record<string, unknown>;\n\ninterface OtelApi {\n trace: {\n getTracer(name: string): {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, Attribute> },\n ): OtelSpan;\n };\n setSpan(ctx: OtelContext, span: OtelSpan): OtelContext;\n };\n context: {\n active(): OtelContext;\n with<T>(ctx: OtelContext, fn: () => T): T;\n };\n SpanStatusCode: { ERROR: number };\n}\n\nlet otelApi: OtelApi | undefined;\nlet otelLoadFailed = false;\n\n/**\n * Lazily load @opentelemetry/api. Returns undefined if the package is not installed.\n */\nasync function getOtelApi(): Promise<OtelApi | undefined> {\n if (otelApi) return otelApi;\n if (otelLoadFailed) return undefined;\n\n try {\n const api = await import(\"@opentelemetry/api\");\n otelApi = api as unknown as OtelApi;\n return otelApi;\n } catch {\n otelLoadFailed = true;\n return undefined;\n }\n}\n\nexport interface TracingSpan {\n span: OtelSpan;\n context: OtelContext;\n}\n\n/**\n * Start an OpenTelemetry span for a tool call using the global tracer provider.\n * Returns undefined if @opentelemetry/api is not available.\n */\nexport async function startToolSpan(\n toolName: string,\n attributes?: Record<string, Attribute>,\n): Promise<TracingSpan | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const tracer = api.trace.getTracer(\"@mcploom/analytics\");\n const span = tracer.startSpan(\"mcp.tool_call\", {\n attributes: {\n \"mcp.tool.name\": toolName,\n ...attributes,\n },\n });\n\n const spanContext = api.trace.setSpan(api.context.active(), span);\n return { span, context: spanContext };\n}\n\n/**\n * End a tool span, setting error status if the call failed.\n */\nexport function endToolSpan(\n tracing: TracingSpan,\n success: boolean,\n errorMessage?: string,\n): void {\n if (!success && otelApi) {\n tracing.span.setStatus({\n code: otelApi.SpanStatusCode.ERROR,\n message: errorMessage,\n });\n }\n tracing.span.end();\n}\n\n/**\n * Run a function within the context of a span, so downstream OTel-instrumented\n * calls become children of this span.\n */\nexport async function withSpanContext<T>(\n tracing: TracingSpan,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const api = otelApi;\n if (!api) return fn();\n return api.context.with(tracing.context, fn);\n}\n","import type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport type { Collector } from \"./collector.js\";\nimport {\n startToolSpan,\n endToolSpan,\n withSpanContext,\n type TracingSpan,\n} from \"./tracing.js\";\nimport type {\n ToolCallEvent,\n InstrumentedTransport,\n SamplingStrategy,\n} from \"./types.js\";\nimport { byteSize } from \"./utils.js\";\n\n/**\n * Checks if a JSON-RPC message is a request (has `id` and `method`).\n */\nfunction isRequest(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n method: string;\n params?: Record<string, unknown>;\n} {\n return \"id\" in msg && \"method\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is a response with a result.\n */\nfunction isResultResponse(\n msg: JSONRPCMessage,\n): msg is JSONRPCMessage & { id: string | number; result: unknown } {\n return \"id\" in msg && \"result\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is an error response.\n */\nfunction isErrorResponse(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n error: { code: number; message: string };\n} {\n return \"id\" in msg && \"error\" in msg;\n}\n\ninterface PendingCall {\n toolName: string;\n startTime: number;\n inputSize: number;\n tracing?: TracingSpan;\n tracingInit?: Promise<TracingSpan | undefined>;\n}\n\n/**\n * Wraps a Transport to intercept tools/call requests and their responses,\n * recording metrics to the Collector.\n */\nexport function instrumentTransport(\n transport: Transport,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n samplingStrategy: SamplingStrategy = \"per_call\",\n): InstrumentedTransport {\n const pending = new Map<string | number, PendingCall>();\n const sessionSamplingDecisions = new Map<string, boolean>();\n\n // Intercept incoming messages (requests from client)\n const origOnMessage = transport.onmessage;\n const origOnClose = transport.onclose;\n\n const sampleForSession = (sessionKey: string): boolean => {\n if (samplingStrategy !== \"per_session\") {\n // Intentional for performance sampling, not security.\n return Math.random() < sampleRate;\n }\n const cached = sessionSamplingDecisions.get(sessionKey);\n if (cached !== undefined) return cached;\n // Intentional for performance sampling, not security.\n const decision = Math.random() < sampleRate;\n sessionSamplingDecisions.set(sessionKey, decision);\n return decision;\n };\n\n const closePendingCall = (\n call: PendingCall,\n success: boolean,\n errorMessage?: string,\n ) => {\n if (call.tracing) {\n endToolSpan(call.tracing, success, errorMessage);\n return;\n }\n if (call.tracingInit) {\n void call.tracingInit\n .then((span) => {\n if (span) {\n endToolSpan(span, success, errorMessage);\n }\n })\n .catch(() => {\n // ignore tracing init failures\n });\n }\n };\n\n const cleanupPendingCalls = (reason: string) => {\n for (const call of pending.values()) {\n closePendingCall(call, false, reason);\n }\n pending.clear();\n sessionSamplingDecisions.clear();\n };\n\n // We need to intercept onmessage being set (since the server sets it after we wrap)\n // The pattern: wrap the transport so that when server sets onmessage, we inject our interceptor\n const proxy = new Proxy(transport, {\n set(target, prop, value) {\n if (prop === \"onmessage\" && typeof value === \"function\") {\n const userHandler = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onmessage = (\n message: JSONRPCMessage,\n extra?: unknown,\n ) => {\n // Intercept incoming tools/call requests\n if (isRequest(message) && message.method === \"tools/call\") {\n const sessionKey = target.sessionId ?? \"unknown\";\n if (sampleForSession(sessionKey)) {\n const params = message.params as\n | { name?: string; arguments?: unknown }\n | undefined;\n const toolName = params?.name ?? \"unknown\";\n const inputSize = byteSize(params?.arguments);\n const pendingCall: PendingCall = {\n toolName,\n startTime: Date.now(),\n inputSize,\n };\n\n // Start span initialization and keep a promise to avoid race with fast responses.\n if (tracing) {\n pendingCall.tracingInit = startToolSpan(toolName, {\n \"mcp.tool.input_size\": inputSize,\n })\n .then((span) => {\n pendingCall.tracing = span;\n return span;\n })\n .catch(() => undefined);\n }\n\n pending.set(message.id, pendingCall);\n }\n }\n userHandler(message, extra);\n };\n return true;\n }\n if (prop === \"onclose\" && typeof value === \"function\") {\n const userOnClose = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onclose = (...args: unknown[]) => {\n cleanupPendingCalls(\"Transport closed before tool response\");\n userOnClose(...args);\n };\n return true;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any)[prop] = value;\n return true;\n },\n get(target, prop, receiver) {\n if (prop === \"send\") {\n // Intercept outgoing messages (responses from server)\n return async (message: JSONRPCMessage, options?: unknown) => {\n if (\"id\" in message) {\n const id = (message as { id: string | number }).id;\n const call = pending.get(id);\n if (call) {\n pending.delete(id);\n const success = isResultResponse(message);\n const errorMessage = isErrorResponse(message)\n ? (message as { error: { message: string } }).error.message\n : undefined;\n\n let outputPayload: unknown;\n if (isResultResponse(message)) {\n outputPayload = (message as { result: unknown }).result;\n } else if (isErrorResponse(message)) {\n outputPayload = (message as { error: unknown }).error;\n }\n\n const event: ToolCallEvent = {\n toolName: call.toolName,\n sessionId: target.sessionId,\n timestamp: call.startTime,\n durationMs: Date.now() - call.startTime,\n success,\n inputSize: call.inputSize,\n outputSize: byteSize(outputPayload),\n ...(isErrorResponse(message) && {\n errorMessage,\n errorCode: (message as { error: { code: number } }).error\n .code,\n }),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n\n // End the tracing span\n closePendingCall(call, success, errorMessage);\n }\n }\n return (target.send as (...args: unknown[]) => unknown).call(\n target,\n message,\n options,\n );\n };\n }\n if (prop === \"close\") {\n return async (...args: unknown[]) => {\n try {\n return await (\n target.close as (...p: unknown[]) => Promise<unknown>\n )(...args);\n } finally {\n cleanupPendingCalls(\"Transport closed before tool response\");\n }\n };\n }\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return value.bind(target);\n }\n return value;\n },\n });\n\n // If onmessage was already set before we wrapped, re-apply through our proxy\n if (origOnMessage) {\n proxy.onmessage = origOnMessage;\n }\n if (origOnClose) {\n proxy.onclose = origOnClose;\n }\n\n return proxy;\n}\n\n/**\n * Wraps a tool handler function to record metrics.\n * Works with McpServer.tool() callback pattern.\n */\nexport function wrapToolHandler<TArgs extends unknown[], TResult>(\n toolName: string,\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n): (...args: TArgs) => Promise<TResult> {\n return async (...args: TArgs) => {\n const shouldSample = Math.random() < sampleRate;\n if (!shouldSample) {\n return handler(...args);\n }\n\n const startTime = Date.now();\n const inputSize = byteSize(args[0]);\n\n // Start a tracing span if enabled\n const tracingSpan = tracing\n ? await startToolSpan(toolName, { \"mcp.tool.input_size\": inputSize })\n : undefined;\n\n try {\n // Run handler within span context so downstream calls become children\n const result = tracingSpan\n ? await withSpanContext(tracingSpan, () => handler(...args))\n : await handler(...args);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: true,\n inputSize,\n outputSize: byteSize(result),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, true);\n return result;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: false,\n errorMessage,\n inputSize,\n outputSize: 0,\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, false, errorMessage);\n throw err;\n }\n };\n}\n","import type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport { Collector } from \"./collector.js\";\nimport { createConsoleExporter } from \"./exporters/console.js\";\nimport { createCustomExporter } from \"./exporters/custom.js\";\nimport { createJsonExporter } from \"./exporters/json.js\";\nimport { createOtlpExporter } from \"./exporters/otlp.js\";\nimport { instrumentTransport, wrapToolHandler } from \"./middleware.js\";\nimport type {\n AnalyticsConfig,\n AnalyticsSnapshot,\n ExporterFn,\n InstrumentedTransport,\n SessionStats,\n SamplingStrategy,\n ToolStats,\n} from \"./types.js\";\n\n/**\n * MCP Analytics — lightweight observability for MCP servers.\n *\n * @example\n * ```ts\n * const analytics = new McpAnalytics({ exporter: \"console\" });\n *\n * // Instrument a transport\n * const tracked = analytics.instrument(transport);\n * await server.connect(tracked);\n *\n * // Or wrap individual handlers\n * server.tool(\"search\", schema, analytics.track(handler));\n *\n * // Get stats\n * console.log(analytics.getStats());\n *\n * // Shutdown\n * await analytics.flush();\n * ```\n */\nexport class McpAnalytics {\n private readonly collector: Collector;\n private readonly sampleRate: number;\n private readonly metadata?: Record<string, string>;\n private readonly tracing: boolean;\n private readonly samplingStrategy: SamplingStrategy;\n\n constructor(config: AnalyticsConfig) {\n this.sampleRate = config.sampleRate ?? 1;\n this.metadata = config.metadata;\n this.tracing = config.tracing ?? false;\n this.samplingStrategy = config.samplingStrategy ?? \"per_call\";\n\n const exporter = this.resolveExporter(config);\n this.collector = new Collector(\n config.maxBufferSize ?? 10_000,\n exporter,\n config.flushIntervalMs ?? 5_000,\n {\n toolWindowSize: config.toolWindowSize,\n },\n );\n }\n\n /**\n * Instrument an MCP transport to automatically track all tool calls.\n * Returns proxy transport that can be used in place of the original.\n */\n instrument(transport: Transport): InstrumentedTransport {\n return instrumentTransport(\n transport,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n this.samplingStrategy,\n );\n }\n\n /**\n * Wrap a tool handler function to track its execution.\n *\n * @example\n * ```ts\n * server.tool(\"search\", schema, analytics.track(async (params) => {\n * return await doSearch(params);\n * }, \"search\"));\n * ```\n */\n track<TArgs extends unknown[], TResult>(\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n toolName?: string,\n ): (...args: TArgs) => Promise<TResult> {\n const name = toolName ?? (handler.name || \"anonymous\");\n return wrapToolHandler(\n name,\n handler,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n );\n }\n\n /**\n * Get a snapshot of all analytics data.\n */\n getStats(): AnalyticsSnapshot {\n return this.collector.getStats();\n }\n\n /**\n * Get stats for a specific tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n return this.collector.getToolStats(toolName);\n }\n\n /**\n * Get stats for a specific session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n return this.collector.getSessionStats(sessionId);\n }\n\n /**\n * Get top sessions ranked by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n return this.collector.getTopSessions(limit);\n }\n\n /**\n * Flush all pending events to the exporter.\n */\n async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.collector.reset();\n }\n\n /**\n * Stop the analytics instance (clears flush timer and flushes remaining events).\n */\n async shutdown(): Promise<void> {\n await this.collector.destroy();\n }\n\n private resolveExporter(config: AnalyticsConfig): ExporterFn {\n if (typeof config.exporter === \"function\") {\n return createCustomExporter(config.exporter);\n }\n\n switch (config.exporter) {\n case \"console\":\n return createConsoleExporter();\n case \"json\": {\n if (!config.json) {\n throw new Error(\n 'McpAnalytics: \"json\" exporter requires a \"json\" config with \"path\"',\n );\n }\n return createJsonExporter(config.json);\n }\n case \"otlp\": {\n if (!config.otlp) {\n throw new Error(\n 'McpAnalytics: \"otlp\" exporter requires an \"otlp\" config with \"endpoint\"',\n );\n }\n return createOtlpExporter(config.otlp);\n }\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["maxBufferSize: number","exporter: ExporterFn","tools: Record<string, ToolStats>","sessions: Record<string, SessionStats>","lines: string[]","tracerPromise: Promise<OtlpTracer> | undefined","otelApi: OtelApi | undefined","pendingCall: PendingCall","outputPayload: unknown","event: ToolCallEvent"],"sources":["../src/utils.ts","../src/collector.ts","../src/exporters/console.ts","../src/exporters/custom.ts","../src/exporters/json.ts","../src/exporters/otlp.ts","../src/tracing.ts","../src/middleware.ts","../src/analytics.ts"],"sourcesContent":["/**\n * Returns the byte length of a string (UTF-8).\n */\nexport function byteSize(value: unknown): number {\n if (value === undefined || value === null) return 0;\n const str = typeof value === \"string\" ? value : JSON.stringify(value);\n return new TextEncoder().encode(str).byteLength;\n}\n\n/**\n * Compute a percentile from a sorted array of numbers.\n * Uses linear interpolation between the closest ranks.\n */\nexport function percentile(sorted: number[], p: number): number {\n if (sorted.length === 0) return 0;\n if (sorted.length === 1) return sorted[0];\n\n const index = (p / 100) * (sorted.length - 1);\n const lower = Math.floor(index);\n const upper = Math.ceil(index);\n\n if (lower === upper) return sorted[lower];\n\n const weight = index - lower;\n return sorted[lower] * (1 - weight) + sorted[upper] * weight;\n}\n\n/**\n * Insert a value into an already-sorted array (ascending), maintaining sort order.\n */\nexport function sortedInsert(arr: number[], value: number): void {\n let lo = 0;\n let hi = arr.length;\n while (lo < hi) {\n const mid = (lo + hi) >>> 1;\n if (arr[mid] < value) {\n lo = mid + 1;\n } else {\n hi = mid;\n }\n }\n arr.splice(lo, 0, value);\n}\n","import type {\n AnalyticsSnapshot,\n ExporterFn,\n SessionStats,\n ToolCallEvent,\n ToolStats,\n} from \"./types.js\";\nimport { percentile } from \"./utils.js\";\n\n/**\n * Per-tool accumulator for computing stats without scanning the full buffer.\n */\ninterface ToolAccumulator {\n count: number;\n errorCount: number;\n totalMs: number;\n /** Sorted durations for percentile computation */\n durations: number[];\n lastCalledAt: number;\n}\n\n/**\n * Per-session accumulator.\n */\ninterface SessionAccumulator extends ToolAccumulator {\n tools: Map<string, ToolAccumulator>;\n}\n\ninterface CollectorOptions {\n toolWindowSize?: number;\n onFlushError?: (error: unknown) => void;\n}\n\n/**\n * In-memory ring buffer that collects ToolCallEvents, computes stats,\n * and periodically flushes to an exporter.\n */\nexport class Collector {\n private readonly buffer: ToolCallEvent[] = [];\n private readonly accumulators = new Map<string, ToolAccumulator>();\n private readonly sessionAccumulators = new Map<string, SessionAccumulator>();\n private totalCalls = 0;\n private totalErrors = 0;\n private readonly startTime = Date.now();\n\n private flushTimer: ReturnType<typeof setInterval> | undefined;\n /** Events accumulated since last flush, to be sent to the exporter */\n private pending: ToolCallEvent[] = [];\n private flushInFlight: Promise<void> | undefined;\n private readonly toolWindowSize: number;\n private readonly onFlushError: (error: unknown) => void;\n\n /**\n * Creates an event collector with bounded buffering and optional periodic flushing.\n */\n constructor(\n private readonly maxBufferSize: number,\n private readonly exporter: ExporterFn,\n flushIntervalMs: number,\n options: CollectorOptions = {},\n ) {\n this.toolWindowSize = Math.max(1, options.toolWindowSize ?? 2_048);\n this.onFlushError =\n options.onFlushError ??\n ((error) => {\n console.error(\"[McpAnalytics] Exporter flush failed:\", error);\n });\n\n if (flushIntervalMs > 0) {\n this.flushTimer = setInterval(() => {\n void this.flush().catch((error) => {\n this.onFlushError(error);\n });\n }, flushIntervalMs);\n // Don't hold the process open for analytics flushing\n if (\n this.flushTimer &&\n typeof this.flushTimer === \"object\" &&\n \"unref\" in this.flushTimer\n ) {\n this.flushTimer.unref();\n }\n }\n }\n\n /**\n * Record a new tool call event.\n */\n record(event: ToolCallEvent): void {\n // Ring buffer: drop oldest when full\n if (this.maxBufferSize > 0) {\n if (this.buffer.length >= this.maxBufferSize) {\n this.buffer.shift();\n }\n this.buffer.push(event);\n }\n this.pending.push(event);\n\n this.totalCalls++;\n if (!event.success) this.totalErrors++;\n\n // Update per-tool accumulator\n this.updateToolAccumulator(this.accumulators, event.toolName, event);\n\n // Update per-session accumulator\n const sessionKey = event.sessionId ?? \"unknown\";\n let sessionAcc = this.sessionAccumulators.get(sessionKey);\n if (!sessionAcc) {\n sessionAcc = { ...this.newAccumulator(), tools: new Map() };\n this.sessionAccumulators.set(sessionKey, sessionAcc);\n }\n this.updateAccumulator(sessionAcc, event);\n this.updateToolAccumulator(sessionAcc.tools, event.toolName, event);\n }\n\n /**\n * Get aggregated stats for all tools.\n */\n getStats(): AnalyticsSnapshot {\n const tools: Record<string, ToolStats> = {};\n for (const [name, acc] of this.accumulators) {\n tools[name] = this.accToStats(acc);\n }\n\n const sessions: Record<string, SessionStats> = {};\n for (const [sessionId, acc] of this.sessionAccumulators) {\n sessions[sessionId] = this.sessionAccToStats(acc);\n }\n\n return {\n totalCalls: this.totalCalls,\n totalErrors: this.totalErrors,\n errorRate: this.totalCalls > 0 ? this.totalErrors / this.totalCalls : 0,\n uptimeMs: Date.now() - this.startTime,\n tools,\n sessions,\n };\n }\n\n /**\n * Get stats for a single tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n const acc = this.accumulators.get(toolName);\n if (!acc) return undefined;\n return this.accToStats(acc);\n }\n\n /**\n * Get stats for a single session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n const acc = this.sessionAccumulators.get(sessionId);\n if (!acc) return undefined;\n return this.sessionAccToStats(acc);\n }\n\n /**\n * Get top sessions ordered by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n if (limit <= 0) return [];\n return [...this.sessionAccumulators.entries()]\n .sort((a, b) => {\n const byCount = b[1].count - a[1].count;\n if (byCount !== 0) return byCount;\n return b[1].lastCalledAt - a[1].lastCalledAt;\n })\n .slice(0, limit)\n .map(([sessionId, acc]) => ({\n sessionId,\n stats: this.sessionAccToStats(acc),\n }));\n }\n\n /**\n * Flush pending events to the exporter.\n */\n async flush(): Promise<void> {\n if (this.flushInFlight) {\n await this.flushInFlight;\n return;\n }\n\n const run = this.flushPending();\n this.flushInFlight = run;\n try {\n await run;\n } finally {\n if (this.flushInFlight === run) {\n this.flushInFlight = undefined;\n }\n }\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.buffer.length = 0;\n this.pending.length = 0;\n this.accumulators.clear();\n this.sessionAccumulators.clear();\n this.totalCalls = 0;\n this.totalErrors = 0;\n }\n\n /**\n * Stop the flush timer and flush remaining events.\n */\n async destroy(): Promise<void> {\n if (this.flushTimer) {\n clearInterval(this.flushTimer);\n this.flushTimer = undefined;\n }\n await this.flush();\n }\n\n private async flushPending(): Promise<void> {\n while (this.pending.length > 0) {\n const batch = this.pending;\n this.pending = [];\n try {\n await this.exporter(batch);\n } catch (error) {\n // Re-queue batch to avoid data loss on transient exporter failures.\n this.pending = batch.concat(this.pending);\n throw error;\n }\n }\n }\n\n private newAccumulator(): ToolAccumulator {\n return {\n count: 0,\n errorCount: 0,\n totalMs: 0,\n durations: [],\n lastCalledAt: 0,\n };\n }\n\n private updateToolAccumulator(\n map: Map<string, ToolAccumulator>,\n toolName: string,\n event: ToolCallEvent,\n ): void {\n let acc = map.get(toolName);\n if (!acc) {\n acc = this.newAccumulator();\n map.set(toolName, acc);\n }\n this.updateAccumulator(acc, event);\n }\n\n private updateAccumulator(acc: ToolAccumulator, event: ToolCallEvent): void {\n acc.count++;\n if (!event.success) acc.errorCount++;\n acc.totalMs += event.durationMs;\n acc.durations.push(event.durationMs);\n if (acc.durations.length > this.toolWindowSize) {\n acc.durations.shift();\n }\n acc.lastCalledAt = event.timestamp;\n }\n\n private accToStats(acc: ToolAccumulator): ToolStats {\n const sortedDurations = [...acc.durations].sort((a, b) => a - b);\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n p50Ms: percentile(sortedDurations, 50),\n p95Ms: percentile(sortedDurations, 95),\n p99Ms: percentile(sortedDurations, 99),\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n };\n }\n\n private sessionAccToStats(acc: SessionAccumulator): SessionStats {\n const tools: Record<string, ToolStats> = {};\n for (const [toolName, toolAcc] of acc.tools) {\n tools[toolName] = this.accToStats(toolAcc);\n }\n\n return {\n count: acc.count,\n errorCount: acc.errorCount,\n errorRate: acc.count > 0 ? acc.errorCount / acc.count : 0,\n avgMs: acc.count > 0 ? acc.totalMs / acc.count : 0,\n lastCalledAt: acc.lastCalledAt,\n tools,\n };\n }\n}\n","import type { ToolCallEvent } from \"../types.js\";\n\n/**\n * Console exporter: pretty-prints each batch of events to stdout.\n */\nexport function createConsoleExporter(): (\n events: ToolCallEvent[],\n) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n\n const lines: string[] = [\"[McpAnalytics] Flushing batch:\"];\n\n for (const e of events) {\n const errorSuffix = e.errorCode ? ` (${e.errorCode})` : \"\";\n const status = e.success ? \"OK\" : `ERR${errorSuffix}`;\n const meta = e.sessionId ? ` session=${e.sessionId}` : \"\";\n lines.push(\n ` ${e.toolName} ${status} ${e.durationMs}ms in=${e.inputSize}B out=${e.outputSize}B${meta}`,\n );\n }\n\n console.log(lines.join(\"\\n\"));\n };\n}\n","import type { ExporterFn, ToolCallEvent } from \"../types.js\";\n\n/**\n * Wraps a user-provided export function, catching errors to prevent\n * exporter failures from disrupting the MCP server.\n */\nexport function createCustomExporter(\n fn: ExporterFn,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n try {\n await fn(events);\n } catch (err) {\n console.error(\"[McpAnalytics] Custom exporter error:\", err);\n }\n };\n}\n","import { appendFile } from \"node:fs/promises\";\n\nimport type { JsonConfig, ToolCallEvent } from \"../types.js\";\n\n/**\n * JSON exporter: appends events as JSONL (one JSON object per line) to a file.\n */\nexport function createJsonExporter(\n config: JsonConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n return async (events) => {\n if (events.length === 0) return;\n const lines = events.map((e) => JSON.stringify(e)).join(\"\\n\") + \"\\n\";\n await appendFile(config.path, lines, \"utf-8\");\n };\n}\n","import type { OtlpConfig, ToolCallEvent } from \"../types.js\";\nimport type { SpanExporter } from \"@opentelemetry/sdk-trace-base\";\n\n/**\n * OTLP exporter: sends tool call events as OpenTelemetry spans.\n *\n * Uses dynamic imports so that @opentelemetry/* packages are only loaded\n * when this exporter is actually used.\n */\nexport function createOtlpExporter(\n config: OtlpConfig,\n): (events: ToolCallEvent[]) => Promise<void> {\n // Lazy-initialized tracer\n let tracerPromise: Promise<OtlpTracer> | undefined;\n\n return async (events) => {\n if (events.length === 0) return;\n\n if (!tracerPromise) {\n tracerPromise = initTracer(config);\n }\n const tracer = await tracerPromise;\n for (const event of events) {\n tracer.exportEvent(event);\n }\n await tracer.flush();\n };\n}\n\ninterface OtlpTracer {\n exportEvent(event: ToolCallEvent): void;\n flush(): Promise<void>;\n}\n\nasync function initTracer(config: OtlpConfig): Promise<OtlpTracer> {\n // Dynamic imports — these only resolve if the user has @opentelemetry installed\n const { SpanStatusCode } = await import(\"@opentelemetry/api\");\n // Create an isolated provider with OTLP HTTP exporter.\n const { BasicTracerProvider, SimpleSpanProcessor } =\n await import(\"@opentelemetry/sdk-trace-base\");\n const { OTLPTraceExporter } =\n await import(\"@opentelemetry/exporter-trace-otlp-http\");\n\n const otlpExporter = new OTLPTraceExporter({\n url: config.endpoint,\n headers: config.headers,\n });\n\n const provider = new BasicTracerProvider({\n spanProcessors: [\n new SimpleSpanProcessor(otlpExporter as unknown as SpanExporter),\n ],\n });\n const tracer = provider.getTracer(\"@mcploom/analytics\");\n\n return {\n exportEvent(event: ToolCallEvent) {\n const span = tracer.startSpan(\"mcp.tool_call\", {\n startTime: new Date(event.timestamp),\n attributes: {\n \"mcp.tool.name\": event.toolName,\n \"mcp.tool.duration_ms\": event.durationMs,\n \"mcp.tool.success\": event.success,\n \"mcp.tool.input_size\": event.inputSize,\n \"mcp.tool.output_size\": event.outputSize,\n ...(event.sessionId && { \"mcp.session.id\": event.sessionId }),\n ...(event.errorMessage && {\n \"mcp.tool.error_message\": event.errorMessage,\n }),\n ...(event.errorCode !== undefined && {\n \"mcp.tool.error_code\": event.errorCode,\n }),\n ...Object.fromEntries(\n Object.entries(event.metadata ?? {}).map(([k, v]) => [\n `mcp.meta.${k}`,\n v,\n ]),\n ),\n },\n });\n\n if (!event.success) {\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: event.errorMessage,\n });\n }\n\n span.end(new Date(event.timestamp + event.durationMs));\n },\n\n async flush() {\n await otlpExporter?.forceFlush?.();\n },\n };\n}\n","/**\n * OpenTelemetry span helpers for MCP tool call tracing.\n *\n * Uses dynamic import so @opentelemetry/api is only loaded when tracing is enabled.\n * When dd-trace (or any OTel-based APM) is configured as the global tracer provider,\n * spans created here automatically appear as children in the existing trace context.\n */\n\ntype Attribute = string | number | boolean;\n\n// Minimal interface matching the subset of @opentelemetry/api we use.\n// This avoids a hard dependency on the package at the type level.\ninterface OtelSpan {\n setAttribute(key: string, value: Attribute): void;\n setStatus(status: { code: number; message?: string }): void;\n end(): void;\n}\n\ntype OtelContext = Record<string, unknown>;\n\ninterface OtelApi {\n trace: {\n getTracer(name: string): {\n startSpan(\n name: string,\n options?: { attributes?: Record<string, Attribute> },\n ): OtelSpan;\n };\n setSpan(ctx: OtelContext, span: OtelSpan): OtelContext;\n };\n context: {\n active(): OtelContext;\n with<T>(ctx: OtelContext, fn: () => T): T;\n };\n SpanStatusCode: { ERROR: number };\n}\n\nlet otelApi: OtelApi | undefined;\nlet otelLoadFailed = false;\n\n/**\n * Lazily load @opentelemetry/api. Returns undefined if the package is not installed.\n */\nasync function getOtelApi(): Promise<OtelApi | undefined> {\n if (otelApi) return otelApi;\n if (otelLoadFailed) return undefined;\n\n try {\n const api = await import(\"@opentelemetry/api\");\n otelApi = api as unknown as OtelApi;\n return otelApi;\n } catch {\n otelLoadFailed = true;\n return undefined;\n }\n}\n\n/**\n * Span handle and context pair returned when analytics tracing starts a tool span.\n */\nexport interface TracingSpan {\n span: OtelSpan;\n context: OtelContext;\n}\n\n/**\n * Start an OpenTelemetry span for a tool call using the global tracer provider.\n * Returns undefined if @opentelemetry/api is not available.\n */\nexport async function startToolSpan(\n toolName: string,\n attributes?: Record<string, Attribute>,\n): Promise<TracingSpan | undefined> {\n const api = await getOtelApi();\n if (!api) return undefined;\n\n const tracer = api.trace.getTracer(\"@mcploom/analytics\");\n const span = tracer.startSpan(\"mcp.tool_call\", {\n attributes: {\n \"mcp.tool.name\": toolName,\n ...attributes,\n },\n });\n\n const spanContext = api.trace.setSpan(api.context.active(), span);\n return { span, context: spanContext };\n}\n\n/**\n * End a tool span, setting error status if the call failed.\n */\nexport function endToolSpan(\n tracing: TracingSpan,\n success: boolean,\n errorMessage?: string,\n): void {\n if (!success && otelApi) {\n tracing.span.setStatus({\n code: otelApi.SpanStatusCode.ERROR,\n message: errorMessage,\n });\n }\n tracing.span.end();\n}\n\n/**\n * Run a function within the context of a span, so downstream OTel-instrumented\n * calls become children of this span.\n */\nexport async function withSpanContext<T>(\n tracing: TracingSpan,\n fn: () => T | Promise<T>,\n): Promise<T> {\n const api = otelApi;\n if (!api) return fn();\n return api.context.with(tracing.context, fn);\n}\n","import type { JSONRPCMessage } from \"@modelcontextprotocol/sdk/types.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport type { Collector } from \"./collector.js\";\nimport {\n startToolSpan,\n endToolSpan,\n withSpanContext,\n type TracingSpan,\n} from \"./tracing.js\";\nimport type {\n ToolCallEvent,\n InstrumentedTransport,\n SamplingStrategy,\n} from \"./types.js\";\nimport { byteSize } from \"./utils.js\";\n\n/**\n * Checks if a JSON-RPC message is a request (has `id` and `method`).\n */\nfunction isRequest(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n method: string;\n params?: Record<string, unknown>;\n} {\n return \"id\" in msg && \"method\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is a response with a result.\n */\nfunction isResultResponse(\n msg: JSONRPCMessage,\n): msg is JSONRPCMessage & { id: string | number; result: unknown } {\n return \"id\" in msg && \"result\" in msg;\n}\n\n/**\n * Checks if a JSON-RPC message is an error response.\n */\nfunction isErrorResponse(msg: JSONRPCMessage): msg is JSONRPCMessage & {\n id: string | number;\n error: { code: number; message: string };\n} {\n return \"id\" in msg && \"error\" in msg;\n}\n\ninterface PendingCall {\n toolName: string;\n startTime: number;\n inputSize: number;\n tracing?: TracingSpan;\n tracingInit?: Promise<TracingSpan | undefined>;\n}\n\n/**\n * Wraps a Transport to intercept tools/call requests and their responses,\n * recording metrics to the Collector.\n */\nexport function instrumentTransport(\n transport: Transport,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n samplingStrategy: SamplingStrategy = \"per_call\",\n): InstrumentedTransport {\n const pending = new Map<string | number, PendingCall>();\n const sessionSamplingDecisions = new Map<string, boolean>();\n\n // Intercept incoming messages (requests from client)\n const origOnMessage = transport.onmessage;\n const origOnClose = transport.onclose;\n\n const sampleForSession = (sessionKey: string): boolean => {\n if (samplingStrategy !== \"per_session\") {\n // Intentional for performance sampling, not security.\n return Math.random() < sampleRate;\n }\n const cached = sessionSamplingDecisions.get(sessionKey);\n if (cached !== undefined) return cached;\n // Intentional for performance sampling, not security.\n const decision = Math.random() < sampleRate;\n sessionSamplingDecisions.set(sessionKey, decision);\n return decision;\n };\n\n const closePendingCall = (\n call: PendingCall,\n success: boolean,\n errorMessage?: string,\n ) => {\n if (call.tracing) {\n endToolSpan(call.tracing, success, errorMessage);\n return;\n }\n if (call.tracingInit) {\n void call.tracingInit\n .then((span) => {\n if (span) {\n endToolSpan(span, success, errorMessage);\n }\n })\n .catch(() => {\n // ignore tracing init failures\n });\n }\n };\n\n const cleanupPendingCalls = (reason: string) => {\n for (const call of pending.values()) {\n closePendingCall(call, false, reason);\n }\n pending.clear();\n sessionSamplingDecisions.clear();\n };\n\n // We need to intercept onmessage being set (since the server sets it after we wrap)\n // The pattern: wrap the transport so that when server sets onmessage, we inject our interceptor\n const proxy = new Proxy(transport, {\n set(target, prop, value) {\n if (prop === \"onmessage\" && typeof value === \"function\") {\n const userHandler = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onmessage = (\n message: JSONRPCMessage,\n extra?: unknown,\n ) => {\n // Intercept incoming tools/call requests\n if (isRequest(message) && message.method === \"tools/call\") {\n const sessionKey = target.sessionId ?? \"unknown\";\n if (sampleForSession(sessionKey)) {\n const params = message.params as\n | { name?: string; arguments?: unknown }\n | undefined;\n const toolName = params?.name ?? \"unknown\";\n const inputSize = byteSize(params?.arguments);\n const pendingCall: PendingCall = {\n toolName,\n startTime: Date.now(),\n inputSize,\n };\n\n // Start span initialization and keep a promise to avoid race with fast responses.\n if (tracing) {\n pendingCall.tracingInit = startToolSpan(toolName, {\n \"mcp.tool.input_size\": inputSize,\n })\n .then((span) => {\n pendingCall.tracing = span;\n return span;\n })\n .catch(() => undefined);\n }\n\n pending.set(message.id, pendingCall);\n }\n }\n userHandler(message, extra);\n };\n return true;\n }\n if (prop === \"onclose\" && typeof value === \"function\") {\n const userOnClose = value;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any).onclose = (...args: unknown[]) => {\n cleanupPendingCalls(\"Transport closed before tool response\");\n userOnClose(...args);\n };\n return true;\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n (target as any)[prop] = value;\n return true;\n },\n get(target, prop, receiver) {\n if (prop === \"send\") {\n // Intercept outgoing messages (responses from server)\n return async (message: JSONRPCMessage, options?: unknown) => {\n if (\"id\" in message) {\n const id = (message as { id: string | number }).id;\n const call = pending.get(id);\n if (call) {\n pending.delete(id);\n const success = isResultResponse(message);\n const errorMessage = isErrorResponse(message)\n ? (message as { error: { message: string } }).error.message\n : undefined;\n\n let outputPayload: unknown;\n if (isResultResponse(message)) {\n outputPayload = (message as { result: unknown }).result;\n } else if (isErrorResponse(message)) {\n outputPayload = (message as { error: unknown }).error;\n }\n\n const event: ToolCallEvent = {\n toolName: call.toolName,\n sessionId: target.sessionId,\n timestamp: call.startTime,\n durationMs: Date.now() - call.startTime,\n success,\n inputSize: call.inputSize,\n outputSize: byteSize(outputPayload),\n ...(isErrorResponse(message) && {\n errorMessage,\n errorCode: (message as { error: { code: number } }).error\n .code,\n }),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n\n // End the tracing span\n closePendingCall(call, success, errorMessage);\n }\n }\n return (target.send as (...args: unknown[]) => unknown).call(\n target,\n message,\n options,\n );\n };\n }\n if (prop === \"close\") {\n return async (...args: unknown[]) => {\n try {\n return await (\n target.close as (...p: unknown[]) => Promise<unknown>\n )(...args);\n } finally {\n cleanupPendingCalls(\"Transport closed before tool response\");\n }\n };\n }\n const value = Reflect.get(target, prop, receiver);\n if (typeof value === \"function\") {\n return value.bind(target);\n }\n return value;\n },\n });\n\n // If onmessage was already set before we wrapped, re-apply through our proxy\n if (origOnMessage) {\n proxy.onmessage = origOnMessage;\n }\n if (origOnClose) {\n proxy.onclose = origOnClose;\n }\n\n return proxy;\n}\n\n/**\n * Wraps a tool handler function to record metrics.\n * Works with McpServer.tool() callback pattern.\n */\nexport function wrapToolHandler<TArgs extends unknown[], TResult>(\n toolName: string,\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n collector: Collector,\n sampleRate: number,\n globalMetadata?: Record<string, string>,\n tracing?: boolean,\n): (...args: TArgs) => Promise<TResult> {\n return async (...args: TArgs) => {\n const shouldSample = Math.random() < sampleRate;\n if (!shouldSample) {\n return handler(...args);\n }\n\n const startTime = Date.now();\n const inputSize = byteSize(args[0]);\n\n // Start a tracing span if enabled\n const tracingSpan = tracing\n ? await startToolSpan(toolName, { \"mcp.tool.input_size\": inputSize })\n : undefined;\n\n try {\n // Run handler within span context so downstream calls become children\n const result = tracingSpan\n ? await withSpanContext(tracingSpan, () => handler(...args))\n : await handler(...args);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: true,\n inputSize,\n outputSize: byteSize(result),\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, true);\n return result;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : String(err);\n const event: ToolCallEvent = {\n toolName,\n timestamp: startTime,\n durationMs: Date.now() - startTime,\n success: false,\n errorMessage,\n inputSize,\n outputSize: 0,\n ...(globalMetadata && { metadata: globalMetadata }),\n };\n collector.record(event);\n if (tracingSpan) endToolSpan(tracingSpan, false, errorMessage);\n throw err;\n }\n };\n}\n","import type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\n\nimport { Collector } from \"./collector.js\";\nimport { createConsoleExporter } from \"./exporters/console.js\";\nimport { createCustomExporter } from \"./exporters/custom.js\";\nimport { createJsonExporter } from \"./exporters/json.js\";\nimport { createOtlpExporter } from \"./exporters/otlp.js\";\nimport { instrumentTransport, wrapToolHandler } from \"./middleware.js\";\nimport type {\n AnalyticsConfig,\n AnalyticsSnapshot,\n ExporterFn,\n InstrumentedTransport,\n SessionStats,\n SamplingStrategy,\n ToolStats,\n} from \"./types.js\";\n\n/**\n * MCP Analytics — lightweight observability for MCP servers.\n *\n * @example\n * ```ts\n * const analytics = new McpAnalytics({ exporter: \"console\" });\n *\n * // Instrument a transport\n * const tracked = analytics.instrument(transport);\n * await server.connect(tracked);\n *\n * // Or wrap individual handlers\n * server.tool(\"search\", schema, analytics.track(handler));\n *\n * // Get stats\n * console.log(analytics.getStats());\n *\n * // Shutdown\n * await analytics.flush();\n * ```\n */\nexport class McpAnalytics {\n private readonly collector: Collector;\n private readonly sampleRate: number;\n private readonly metadata?: Record<string, string>;\n private readonly tracing: boolean;\n private readonly samplingStrategy: SamplingStrategy;\n\n /**\n * Creates an analytics instance with the configured exporter, buffering, and sampling behavior.\n */\n constructor(config: AnalyticsConfig) {\n this.sampleRate = config.sampleRate ?? 1;\n this.metadata = config.metadata;\n this.tracing = config.tracing ?? false;\n this.samplingStrategy = config.samplingStrategy ?? \"per_call\";\n\n const exporter = this.resolveExporter(config);\n this.collector = new Collector(\n config.maxBufferSize ?? 10_000,\n exporter,\n config.flushIntervalMs ?? 5_000,\n {\n toolWindowSize: config.toolWindowSize,\n },\n );\n }\n\n /**\n * Instrument an MCP transport to automatically track all tool calls.\n * Returns proxy transport that can be used in place of the original.\n */\n instrument(transport: Transport): InstrumentedTransport {\n return instrumentTransport(\n transport,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n this.samplingStrategy,\n );\n }\n\n /**\n * Wrap a tool handler function to track its execution.\n *\n * @example\n * ```ts\n * server.tool(\"search\", schema, analytics.track(async (params) => {\n * return await doSearch(params);\n * }, \"search\"));\n * ```\n */\n track<TArgs extends unknown[], TResult>(\n handler: (...args: TArgs) => TResult | Promise<TResult>,\n toolName?: string,\n ): (...args: TArgs) => Promise<TResult> {\n const name = toolName ?? (handler.name || \"anonymous\");\n return wrapToolHandler(\n name,\n handler,\n this.collector,\n this.sampleRate,\n this.metadata,\n this.tracing,\n );\n }\n\n /**\n * Get a snapshot of all analytics data.\n */\n getStats(): AnalyticsSnapshot {\n return this.collector.getStats();\n }\n\n /**\n * Get stats for a specific tool.\n */\n getToolStats(toolName: string): ToolStats | undefined {\n return this.collector.getToolStats(toolName);\n }\n\n /**\n * Get stats for a specific session.\n */\n getSessionStats(sessionId: string): SessionStats | undefined {\n return this.collector.getSessionStats(sessionId);\n }\n\n /**\n * Get top sessions ranked by total call count.\n */\n getTopSessions(\n limit = 10,\n ): Array<{ sessionId: string; stats: SessionStats }> {\n return this.collector.getTopSessions(limit);\n }\n\n /**\n * Flush all pending events to the exporter.\n */\n async flush(): Promise<void> {\n await this.collector.flush();\n }\n\n /**\n * Reset all collected data.\n */\n reset(): void {\n this.collector.reset();\n }\n\n /**\n * Stop the analytics instance (clears flush timer and flushes remaining events).\n */\n async shutdown(): Promise<void> {\n await this.collector.destroy();\n }\n\n private resolveExporter(config: AnalyticsConfig): ExporterFn {\n if (typeof config.exporter === \"function\") {\n return createCustomExporter(config.exporter);\n }\n\n switch (config.exporter) {\n case \"console\":\n return createConsoleExporter();\n case \"json\": {\n if (!config.json) {\n throw new Error(\n 'McpAnalytics: \"json\" exporter requires a \"json\" config with \"path\"',\n );\n }\n return createJsonExporter(config.json);\n }\n case \"otlp\": {\n if (!config.otlp) {\n throw new Error(\n 'McpAnalytics: \"otlp\" exporter requires an \"otlp\" config with \"endpoint\"',\n );\n }\n return createOtlpExporter(config.otlp);\n }\n }\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mcploom/analytics",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Lightweight analytics and observability for MCP servers.",
5
5
  "license": "MIT",
6
6
  "type": "module",