@gomcp/analytics 0.1.0
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/LICENSE +21 -0
- package/README.md +231 -0
- package/dist/analytics.d.ts +68 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +113 -0
- package/dist/analytics.js.map +1 -0
- package/dist/collector.d.ts +44 -0
- package/dist/collector.d.ts.map +1 -0
- package/dist/collector.js +132 -0
- package/dist/collector.js.map +1 -0
- package/dist/exporters/console.d.ts +6 -0
- package/dist/exporters/console.d.ts.map +1 -0
- package/dist/exporters/console.js +18 -0
- package/dist/exporters/console.js.map +1 -0
- package/dist/exporters/custom.d.ts +7 -0
- package/dist/exporters/custom.d.ts.map +1 -0
- package/dist/exporters/custom.js +15 -0
- package/dist/exporters/custom.js.map +1 -0
- package/dist/exporters/json.d.ts +6 -0
- package/dist/exporters/json.d.ts.map +1 -0
- package/dist/exporters/json.js +13 -0
- package/dist/exporters/json.js.map +1 -0
- package/dist/exporters/otlp.d.ts +9 -0
- package/dist/exporters/otlp.d.ts.map +1 -0
- package/dist/exporters/otlp.js +83 -0
- package/dist/exporters/otlp.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +14 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +184 -0
- package/dist/middleware.js.map +1 -0
- package/dist/tracing.d.ts +37 -0
- package/dist/tracing.d.ts.map +1 -0
- package/dist/tracing.js +67 -0
- package/dist/tracing.js.map +1 -0
- package/dist/types.d.ts +98 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/utils.d.ts +14 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +44 -0
- package/dist/utils.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/exporters/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D;;;GAGG;AACH,wBAAgB,oBAAoB,CAClC,EAAE,EAAE,UAAU,GACb,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAQ5C"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a user-provided export function, catching errors to prevent
|
|
3
|
+
* exporter failures from disrupting the MCP server.
|
|
4
|
+
*/
|
|
5
|
+
export function createCustomExporter(fn) {
|
|
6
|
+
return async (events) => {
|
|
7
|
+
try {
|
|
8
|
+
await fn(events);
|
|
9
|
+
}
|
|
10
|
+
catch (err) {
|
|
11
|
+
console.error("[McpAnalytics] Custom exporter error:", err);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=custom.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/exporters/custom.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,EAAc;IAEd,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE;QACtB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,uCAAuC,EAAE,GAAG,CAAC,CAAC;QAC9D,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { JsonConfig, ToolCallEvent } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* JSON exporter: appends events as JSONL (one JSON object per line) to a file.
|
|
4
|
+
*/
|
|
5
|
+
export declare function createJsonExporter(config: JsonConfig): (events: ToolCallEvent[]) => Promise<void>;
|
|
6
|
+
//# sourceMappingURL=json.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/exporters/json.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D;;GAEG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,GACjB,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAM5C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { appendFile } from "node:fs/promises";
|
|
2
|
+
/**
|
|
3
|
+
* JSON exporter: appends events as JSONL (one JSON object per line) to a file.
|
|
4
|
+
*/
|
|
5
|
+
export function createJsonExporter(config) {
|
|
6
|
+
return async (events) => {
|
|
7
|
+
if (events.length === 0)
|
|
8
|
+
return;
|
|
9
|
+
const lines = events.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
10
|
+
await appendFile(config.path, lines, "utf-8");
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=json.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json.js","sourceRoot":"","sources":["../../src/exporters/json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAI9C;;GAEG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAkB;IAElB,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE;QACtB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QACrE,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAChD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OtlpConfig, ToolCallEvent } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* OTLP exporter: sends tool call events as OpenTelemetry spans.
|
|
4
|
+
*
|
|
5
|
+
* Uses dynamic imports so that @opentelemetry/* packages are only loaded
|
|
6
|
+
* when this exporter is actually used.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createOtlpExporter(config: OtlpConfig): (events: ToolCallEvent[]) => Promise<void>;
|
|
9
|
+
//# sourceMappingURL=otlp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp.d.ts","sourceRoot":"","sources":["../../src/exporters/otlp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,UAAU,GACjB,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAgB5C"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP exporter: sends tool call events as OpenTelemetry spans.
|
|
3
|
+
*
|
|
4
|
+
* Uses dynamic imports so that @opentelemetry/* packages are only loaded
|
|
5
|
+
* when this exporter is actually used.
|
|
6
|
+
*/
|
|
7
|
+
export function createOtlpExporter(config) {
|
|
8
|
+
// Lazy-initialized tracer
|
|
9
|
+
let tracerPromise;
|
|
10
|
+
return async (events) => {
|
|
11
|
+
if (events.length === 0)
|
|
12
|
+
return;
|
|
13
|
+
if (!tracerPromise) {
|
|
14
|
+
tracerPromise = initTracer(config);
|
|
15
|
+
}
|
|
16
|
+
const tracer = await tracerPromise;
|
|
17
|
+
for (const event of events) {
|
|
18
|
+
tracer.exportEvent(event);
|
|
19
|
+
}
|
|
20
|
+
await tracer.flush();
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
async function initTracer(config) {
|
|
24
|
+
// Dynamic imports — these only resolve if the user has @opentelemetry installed
|
|
25
|
+
const { trace, SpanStatusCode } = await import("@opentelemetry/api");
|
|
26
|
+
let tracer;
|
|
27
|
+
let otlpExporter;
|
|
28
|
+
if (config.useGlobalProvider) {
|
|
29
|
+
// Use the global tracer provider (e.g. dd-trace registers itself here)
|
|
30
|
+
tracer = trace.getTracer("@gomcp/analytics");
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// Create an isolated provider with OTLP HTTP exporter
|
|
34
|
+
const { BasicTracerProvider, SimpleSpanProcessor } = await import("@opentelemetry/sdk-trace-base");
|
|
35
|
+
const { OTLPTraceExporter } = await import("@opentelemetry/exporter-trace-otlp-http");
|
|
36
|
+
otlpExporter = new OTLPTraceExporter({
|
|
37
|
+
url: config.endpoint,
|
|
38
|
+
headers: config.headers,
|
|
39
|
+
});
|
|
40
|
+
const provider = new BasicTracerProvider({
|
|
41
|
+
spanProcessors: [
|
|
42
|
+
new SimpleSpanProcessor(otlpExporter),
|
|
43
|
+
],
|
|
44
|
+
});
|
|
45
|
+
tracer = provider.getTracer("@gomcp/analytics");
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
exportEvent(event) {
|
|
49
|
+
const span = tracer.startSpan("mcp.tool_call", {
|
|
50
|
+
startTime: new Date(event.timestamp),
|
|
51
|
+
attributes: {
|
|
52
|
+
"mcp.tool.name": event.toolName,
|
|
53
|
+
"mcp.tool.duration_ms": event.durationMs,
|
|
54
|
+
"mcp.tool.success": event.success,
|
|
55
|
+
"mcp.tool.input_size": event.inputSize,
|
|
56
|
+
"mcp.tool.output_size": event.outputSize,
|
|
57
|
+
...(event.sessionId && { "mcp.session.id": event.sessionId }),
|
|
58
|
+
...(event.errorMessage && {
|
|
59
|
+
"mcp.tool.error_message": event.errorMessage,
|
|
60
|
+
}),
|
|
61
|
+
...(event.errorCode !== undefined && {
|
|
62
|
+
"mcp.tool.error_code": event.errorCode,
|
|
63
|
+
}),
|
|
64
|
+
...Object.fromEntries(Object.entries(event.metadata ?? {}).map(([k, v]) => [
|
|
65
|
+
`mcp.meta.${k}`,
|
|
66
|
+
v,
|
|
67
|
+
])),
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
if (!event.success) {
|
|
71
|
+
span.setStatus({
|
|
72
|
+
code: SpanStatusCode.ERROR,
|
|
73
|
+
message: event.errorMessage,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
span.end(new Date(event.timestamp + event.durationMs));
|
|
77
|
+
},
|
|
78
|
+
async flush() {
|
|
79
|
+
await otlpExporter?.forceFlush?.();
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=otlp.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"otlp.js","sourceRoot":"","sources":["../../src/exporters/otlp.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,UAAU,EACV,qBAAqB,EACrB,UAAU,EACV,UAAU,EACV,aAAa,EACb,SAAS,GACV,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
import type { Collector } from "./collector.js";
|
|
3
|
+
import type { InstrumentedTransport } from "./types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Wraps a Transport to intercept tools/call requests and their responses,
|
|
6
|
+
* recording metrics to the Collector.
|
|
7
|
+
*/
|
|
8
|
+
export declare function instrumentTransport(transport: Transport, collector: Collector, sampleRate: number, globalMetadata?: Record<string, string>, tracing?: boolean): InstrumentedTransport;
|
|
9
|
+
/**
|
|
10
|
+
* Wraps a tool handler function to record metrics.
|
|
11
|
+
* Works with McpServer.tool() callback pattern.
|
|
12
|
+
*/
|
|
13
|
+
export declare function wrapToolHandler<TArgs extends unknown[], TResult>(toolName: string, handler: (...args: TArgs) => TResult | Promise<TResult>, collector: Collector, sampleRate: number, globalMetadata?: Record<string, string>, tracing?: boolean): (...args: TArgs) => Promise<TResult>;
|
|
14
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAOhD,OAAO,KAAK,EAAiB,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAwCvE;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,SAAS,EAAE,SAAS,EACpB,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,OAAO,CAAC,EAAE,OAAO,GAChB,qBAAqB,CAwHvB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,SAAS,OAAO,EAAE,EAAE,OAAO,EAC9D,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EACvD,SAAS,EAAE,SAAS,EACpB,UAAU,EAAE,MAAM,EAClB,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACvC,OAAO,CAAC,EAAE,OAAO,GAChB,CAAC,GAAG,IAAI,EAAE,KAAK,KAAK,OAAO,CAAC,OAAO,CAAC,CAiDtC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { startToolSpan, endToolSpan, withSpanContext, } from "./tracing.js";
|
|
2
|
+
import { byteSize } from "./utils.js";
|
|
3
|
+
/**
|
|
4
|
+
* Checks if a JSON-RPC message is a request (has `id` and `method`).
|
|
5
|
+
*/
|
|
6
|
+
function isRequest(msg) {
|
|
7
|
+
return "id" in msg && "method" in msg;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Checks if a JSON-RPC message is a response with a result.
|
|
11
|
+
*/
|
|
12
|
+
function isResultResponse(msg) {
|
|
13
|
+
return "id" in msg && "result" in msg;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a JSON-RPC message is an error response.
|
|
17
|
+
*/
|
|
18
|
+
function isErrorResponse(msg) {
|
|
19
|
+
return "id" in msg && "error" in msg;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Wraps a Transport to intercept tools/call requests and their responses,
|
|
23
|
+
* recording metrics to the Collector.
|
|
24
|
+
*/
|
|
25
|
+
export function instrumentTransport(transport, collector, sampleRate, globalMetadata, tracing) {
|
|
26
|
+
const pending = new Map();
|
|
27
|
+
// Intercept incoming messages (requests from client)
|
|
28
|
+
const origOnMessage = transport.onmessage;
|
|
29
|
+
// We need to intercept onmessage being set (since the server sets it after we wrap)
|
|
30
|
+
// The pattern: wrap the transport so that when server sets onmessage, we inject our interceptor
|
|
31
|
+
const proxy = new Proxy(transport, {
|
|
32
|
+
// eslint-disable-next-line sonarjs/no-invariant-returns -- Proxy set traps must always return true
|
|
33
|
+
set(target, prop, value) {
|
|
34
|
+
if (prop === "onmessage" && typeof value === "function") {
|
|
35
|
+
const userHandler = value;
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
37
|
+
target.onmessage = (message, extra) => {
|
|
38
|
+
// Intercept incoming tools/call requests
|
|
39
|
+
if (isRequest(message) && message.method === "tools/call") {
|
|
40
|
+
// eslint-disable-next-line sonarjs/pseudo-random -- intentional for perf sampling, not security
|
|
41
|
+
if (Math.random() < sampleRate) {
|
|
42
|
+
const params = message.params;
|
|
43
|
+
const toolName = params?.name ?? "unknown";
|
|
44
|
+
const inputSize = byteSize(params?.arguments);
|
|
45
|
+
const pendingCall = {
|
|
46
|
+
toolName,
|
|
47
|
+
startTime: Date.now(),
|
|
48
|
+
inputSize,
|
|
49
|
+
};
|
|
50
|
+
pending.set(message.id, pendingCall);
|
|
51
|
+
// Start a tracing span (async, fire-and-forget into the pending map)
|
|
52
|
+
if (tracing) {
|
|
53
|
+
startToolSpan(toolName, {
|
|
54
|
+
"mcp.tool.input_size": inputSize,
|
|
55
|
+
}).then((span) => {
|
|
56
|
+
const entry = pending.get(message.id);
|
|
57
|
+
if (entry && span) {
|
|
58
|
+
entry.tracing = span;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
userHandler(message, extra);
|
|
65
|
+
};
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
69
|
+
target[prop] = value;
|
|
70
|
+
return true;
|
|
71
|
+
},
|
|
72
|
+
get(target, prop, receiver) {
|
|
73
|
+
if (prop === "send") {
|
|
74
|
+
// Intercept outgoing messages (responses from server)
|
|
75
|
+
return async (message, options) => {
|
|
76
|
+
if ("id" in message) {
|
|
77
|
+
const id = message.id;
|
|
78
|
+
const call = pending.get(id);
|
|
79
|
+
if (call) {
|
|
80
|
+
pending.delete(id);
|
|
81
|
+
const success = isResultResponse(message);
|
|
82
|
+
const errorMessage = isErrorResponse(message)
|
|
83
|
+
? message.error.message
|
|
84
|
+
: undefined;
|
|
85
|
+
let outputPayload;
|
|
86
|
+
if (isResultResponse(message)) {
|
|
87
|
+
outputPayload = message.result;
|
|
88
|
+
}
|
|
89
|
+
else if (isErrorResponse(message)) {
|
|
90
|
+
outputPayload = message.error;
|
|
91
|
+
}
|
|
92
|
+
const event = {
|
|
93
|
+
toolName: call.toolName,
|
|
94
|
+
sessionId: target.sessionId,
|
|
95
|
+
timestamp: call.startTime,
|
|
96
|
+
durationMs: Date.now() - call.startTime,
|
|
97
|
+
success,
|
|
98
|
+
inputSize: call.inputSize,
|
|
99
|
+
outputSize: byteSize(outputPayload),
|
|
100
|
+
...(isErrorResponse(message) && {
|
|
101
|
+
errorMessage,
|
|
102
|
+
errorCode: message.error
|
|
103
|
+
.code,
|
|
104
|
+
}),
|
|
105
|
+
...(globalMetadata && { metadata: globalMetadata }),
|
|
106
|
+
};
|
|
107
|
+
collector.record(event);
|
|
108
|
+
// End the tracing span
|
|
109
|
+
if (call.tracing) {
|
|
110
|
+
endToolSpan(call.tracing, success, errorMessage);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return target.send.call(target, message, options);
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
const value = Reflect.get(target, prop, receiver);
|
|
118
|
+
if (typeof value === "function") {
|
|
119
|
+
return value.bind(target);
|
|
120
|
+
}
|
|
121
|
+
return value;
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
// If onmessage was already set before we wrapped, re-apply through our proxy
|
|
125
|
+
if (origOnMessage) {
|
|
126
|
+
proxy.onmessage = origOnMessage;
|
|
127
|
+
}
|
|
128
|
+
return proxy;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Wraps a tool handler function to record metrics.
|
|
132
|
+
* Works with McpServer.tool() callback pattern.
|
|
133
|
+
*/
|
|
134
|
+
export function wrapToolHandler(toolName, handler, collector, sampleRate, globalMetadata, tracing) {
|
|
135
|
+
return async (...args) => {
|
|
136
|
+
const shouldSample = Math.random() < sampleRate; // eslint-disable-line sonarjs/pseudo-random -- intentional for perf sampling, not security
|
|
137
|
+
if (!shouldSample) {
|
|
138
|
+
return handler(...args);
|
|
139
|
+
}
|
|
140
|
+
const startTime = Date.now();
|
|
141
|
+
const inputSize = byteSize(args[0]);
|
|
142
|
+
// Start a tracing span if enabled
|
|
143
|
+
const tracingSpan = tracing
|
|
144
|
+
? await startToolSpan(toolName, { "mcp.tool.input_size": inputSize })
|
|
145
|
+
: undefined;
|
|
146
|
+
try {
|
|
147
|
+
// Run handler within span context so downstream calls become children
|
|
148
|
+
const result = tracingSpan
|
|
149
|
+
? await withSpanContext(tracingSpan, () => handler(...args))
|
|
150
|
+
: await handler(...args);
|
|
151
|
+
const event = {
|
|
152
|
+
toolName,
|
|
153
|
+
timestamp: startTime,
|
|
154
|
+
durationMs: Date.now() - startTime,
|
|
155
|
+
success: true,
|
|
156
|
+
inputSize,
|
|
157
|
+
outputSize: byteSize(result),
|
|
158
|
+
...(globalMetadata && { metadata: globalMetadata }),
|
|
159
|
+
};
|
|
160
|
+
collector.record(event);
|
|
161
|
+
if (tracingSpan)
|
|
162
|
+
endToolSpan(tracingSpan, true);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
catch (err) {
|
|
166
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
167
|
+
const event = {
|
|
168
|
+
toolName,
|
|
169
|
+
timestamp: startTime,
|
|
170
|
+
durationMs: Date.now() - startTime,
|
|
171
|
+
success: false,
|
|
172
|
+
errorMessage,
|
|
173
|
+
inputSize,
|
|
174
|
+
outputSize: 0,
|
|
175
|
+
...(globalMetadata && { metadata: globalMetadata }),
|
|
176
|
+
};
|
|
177
|
+
collector.record(event);
|
|
178
|
+
if (tracingSpan)
|
|
179
|
+
endToolSpan(tracingSpan, false, errorMessage);
|
|
180
|
+
throw err;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry span helpers for MCP tool call tracing.
|
|
3
|
+
*
|
|
4
|
+
* Uses dynamic import so @opentelemetry/api is only loaded when tracing is enabled.
|
|
5
|
+
* When dd-trace (or any OTel-based APM) is configured as the global tracer provider,
|
|
6
|
+
* spans created here automatically appear as children in the existing trace context.
|
|
7
|
+
*/
|
|
8
|
+
type Attribute = string | number | boolean;
|
|
9
|
+
interface OtelSpan {
|
|
10
|
+
setAttribute(key: string, value: Attribute): void;
|
|
11
|
+
setStatus(status: {
|
|
12
|
+
code: number;
|
|
13
|
+
message?: string;
|
|
14
|
+
}): void;
|
|
15
|
+
end(): void;
|
|
16
|
+
}
|
|
17
|
+
type OtelContext = Record<string, unknown>;
|
|
18
|
+
export interface TracingSpan {
|
|
19
|
+
span: OtelSpan;
|
|
20
|
+
context: OtelContext;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Start an OpenTelemetry span for a tool call using the global tracer provider.
|
|
24
|
+
* Returns undefined if @opentelemetry/api is not available.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startToolSpan(toolName: string, attributes?: Record<string, Attribute>): Promise<TracingSpan | undefined>;
|
|
27
|
+
/**
|
|
28
|
+
* End a tool span, setting error status if the call failed.
|
|
29
|
+
*/
|
|
30
|
+
export declare function endToolSpan(tracing: TracingSpan, success: boolean, errorMessage?: string): void;
|
|
31
|
+
/**
|
|
32
|
+
* Run a function within the context of a span, so downstream OTel-instrumented
|
|
33
|
+
* calls become children of this span.
|
|
34
|
+
*/
|
|
35
|
+
export declare function withSpanContext<T>(tracing: TracingSpan, fn: () => T | Promise<T>): Promise<T>;
|
|
36
|
+
export {};
|
|
37
|
+
//# sourceMappingURL=tracing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.d.ts","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,KAAK,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAI3C,UAAU,QAAQ;IAChB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IAClD,SAAS,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAC5D,GAAG,IAAI,IAAI,CAAC;CACb;AAED,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAsC3C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,QAAQ,CAAC;IACf,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GACrC,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CAalC;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,OAAO,EAAE,WAAW,EACpB,OAAO,EAAE,OAAO,EAChB,YAAY,CAAC,EAAE,MAAM,GACpB,IAAI,CAQN;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,CAAC,EACrC,OAAO,EAAE,WAAW,EACpB,EAAE,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACvB,OAAO,CAAC,CAAC,CAAC,CAIZ"}
|
package/dist/tracing.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenTelemetry span helpers for MCP tool call tracing.
|
|
3
|
+
*
|
|
4
|
+
* Uses dynamic import so @opentelemetry/api is only loaded when tracing is enabled.
|
|
5
|
+
* When dd-trace (or any OTel-based APM) is configured as the global tracer provider,
|
|
6
|
+
* spans created here automatically appear as children in the existing trace context.
|
|
7
|
+
*/
|
|
8
|
+
let otelApi;
|
|
9
|
+
let otelLoadFailed = false;
|
|
10
|
+
/**
|
|
11
|
+
* Lazily load @opentelemetry/api. Returns undefined if the package is not installed.
|
|
12
|
+
*/
|
|
13
|
+
async function getOtelApi() {
|
|
14
|
+
if (otelApi)
|
|
15
|
+
return otelApi;
|
|
16
|
+
if (otelLoadFailed)
|
|
17
|
+
return undefined;
|
|
18
|
+
try {
|
|
19
|
+
const api = await import("@opentelemetry/api");
|
|
20
|
+
otelApi = api;
|
|
21
|
+
return otelApi;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
otelLoadFailed = true;
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Start an OpenTelemetry span for a tool call using the global tracer provider.
|
|
30
|
+
* Returns undefined if @opentelemetry/api is not available.
|
|
31
|
+
*/
|
|
32
|
+
export async function startToolSpan(toolName, attributes) {
|
|
33
|
+
const api = await getOtelApi();
|
|
34
|
+
if (!api)
|
|
35
|
+
return undefined;
|
|
36
|
+
const tracer = api.trace.getTracer("@gomcp/analytics");
|
|
37
|
+
const span = tracer.startSpan("mcp.tool_call", {
|
|
38
|
+
attributes: {
|
|
39
|
+
"mcp.tool.name": toolName,
|
|
40
|
+
...attributes,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
return { span, context: api.context.active() };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* End a tool span, setting error status if the call failed.
|
|
47
|
+
*/
|
|
48
|
+
export function endToolSpan(tracing, success, errorMessage) {
|
|
49
|
+
if (!success && otelApi) {
|
|
50
|
+
tracing.span.setStatus({
|
|
51
|
+
code: otelApi.SpanStatusCode.ERROR,
|
|
52
|
+
message: errorMessage,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
tracing.span.end();
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run a function within the context of a span, so downstream OTel-instrumented
|
|
59
|
+
* calls become children of this span.
|
|
60
|
+
*/
|
|
61
|
+
export async function withSpanContext(tracing, fn) {
|
|
62
|
+
const api = otelApi;
|
|
63
|
+
if (!api)
|
|
64
|
+
return fn();
|
|
65
|
+
return api.context.with(tracing.context, fn);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=tracing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tracing.js","sourceRoot":"","sources":["../src/tracing.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AA8BH,IAAI,OAA4B,CAAC;AACjC,IAAI,cAAc,GAAG,KAAK,CAAC;AAE3B;;GAEG;AACH,KAAK,UAAU,UAAU;IACvB,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAC5B,IAAI,cAAc;QAAE,OAAO,SAAS,CAAC;IAErC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAC;QAC/C,OAAO,GAAG,GAAyB,CAAC;QACpC,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,MAAM,CAAC;QACP,cAAc,GAAG,IAAI,CAAC;QACtB,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAOD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,UAAsC;IAEtC,MAAM,GAAG,GAAG,MAAM,UAAU,EAAE,CAAC;IAC/B,IAAI,CAAC,GAAG;QAAE,OAAO,SAAS,CAAC;IAE3B,MAAM,MAAM,GAAG,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,kBAAkB,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,eAAe,EAAE;QAC7C,UAAU,EAAE;YACV,eAAe,EAAE,QAAQ;YACzB,GAAG,UAAU;SACd;KACF,CAAC,CAAC;IAEH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;AACjD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW,CACzB,OAAoB,EACpB,OAAgB,EAChB,YAAqB;IAErB,IAAI,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC;YACrB,IAAI,EAAE,OAAO,CAAC,cAAc,CAAC,KAAK;YAClC,OAAO,EAAE,YAAY;SACtB,CAAC,CAAC;IACL,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAoB,EACpB,EAAwB;IAExB,MAAM,GAAG,GAAG,OAAO,CAAC;IACpB,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,EAAE,CAAC;IACtB,OAAO,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AAC/C,CAAC"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
|
|
2
|
+
/**
|
|
3
|
+
* A single tool call event recorded by the collector.
|
|
4
|
+
*/
|
|
5
|
+
export interface ToolCallEvent {
|
|
6
|
+
/** Tool name from the tools/call request */
|
|
7
|
+
toolName: string;
|
|
8
|
+
/** Session ID from the transport, if available */
|
|
9
|
+
sessionId?: string;
|
|
10
|
+
/** Unix timestamp in milliseconds when the call started */
|
|
11
|
+
timestamp: number;
|
|
12
|
+
/** Duration in milliseconds */
|
|
13
|
+
durationMs: number;
|
|
14
|
+
/** Whether the call completed successfully */
|
|
15
|
+
success: boolean;
|
|
16
|
+
/** Error message if the call failed */
|
|
17
|
+
errorMessage?: string;
|
|
18
|
+
/** JSON-RPC error code if applicable */
|
|
19
|
+
errorCode?: number;
|
|
20
|
+
/** Size of serialized input arguments in bytes */
|
|
21
|
+
inputSize: number;
|
|
22
|
+
/** Size of serialized output in bytes */
|
|
23
|
+
outputSize: number;
|
|
24
|
+
/** User-provided metadata */
|
|
25
|
+
metadata?: Record<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Aggregated stats for a single tool.
|
|
29
|
+
*/
|
|
30
|
+
export interface ToolStats {
|
|
31
|
+
count: number;
|
|
32
|
+
errorCount: number;
|
|
33
|
+
errorRate: number;
|
|
34
|
+
p50Ms: number;
|
|
35
|
+
p95Ms: number;
|
|
36
|
+
p99Ms: number;
|
|
37
|
+
avgMs: number;
|
|
38
|
+
lastCalledAt: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Snapshot of all analytics data.
|
|
42
|
+
*/
|
|
43
|
+
export interface AnalyticsSnapshot {
|
|
44
|
+
totalCalls: number;
|
|
45
|
+
totalErrors: number;
|
|
46
|
+
errorRate: number;
|
|
47
|
+
uptimeMs: number;
|
|
48
|
+
tools: Record<string, ToolStats>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Custom exporter function signature.
|
|
52
|
+
*/
|
|
53
|
+
export type ExporterFn = (events: ToolCallEvent[]) => Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* OTLP exporter configuration.
|
|
56
|
+
*/
|
|
57
|
+
export interface OtlpConfig {
|
|
58
|
+
endpoint: string;
|
|
59
|
+
headers?: Record<string, string>;
|
|
60
|
+
/** When true, use the global TracerProvider instead of creating an isolated one.
|
|
61
|
+
* This is useful when dd-trace or another APM registers as the global OTel provider. */
|
|
62
|
+
useGlobalProvider?: boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* JSON file exporter configuration.
|
|
66
|
+
*/
|
|
67
|
+
export interface JsonConfig {
|
|
68
|
+
path: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Configuration for McpAnalytics.
|
|
72
|
+
*/
|
|
73
|
+
export interface AnalyticsConfig {
|
|
74
|
+
/** Built-in exporter name or a custom export function */
|
|
75
|
+
exporter: "console" | "json" | "otlp" | ExporterFn;
|
|
76
|
+
/** OTLP configuration (required when exporter is "otlp") */
|
|
77
|
+
otlp?: OtlpConfig;
|
|
78
|
+
/** JSON file configuration (required when exporter is "json") */
|
|
79
|
+
json?: JsonConfig;
|
|
80
|
+
/** Fraction of calls to sample (0.0 to 1.0). Default: 1.0 */
|
|
81
|
+
sampleRate?: number;
|
|
82
|
+
/** How often to flush batched events in ms. Default: 5000 */
|
|
83
|
+
flushIntervalMs?: number;
|
|
84
|
+
/** Maximum events to keep in the ring buffer. Default: 10000 */
|
|
85
|
+
maxBufferSize?: number;
|
|
86
|
+
/** User-provided metadata added to every event */
|
|
87
|
+
metadata?: Record<string, string>;
|
|
88
|
+
/** Enable OpenTelemetry span creation during tool execution.
|
|
89
|
+
* When true, uses the global tracer provider (compatible with dd-trace, etc.)
|
|
90
|
+
* Default: false */
|
|
91
|
+
tracing?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* A transport that has been instrumented with analytics.
|
|
95
|
+
* Same interface as Transport — can be used anywhere a Transport is expected.
|
|
96
|
+
*/
|
|
97
|
+
export type InstrumentedTransport = Transport;
|
|
98
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAC;AAE/E;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,4CAA4C;IAC5C,QAAQ,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2DAA2D;IAC3D,SAAS,EAAE,MAAM,CAAC;IAClB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,8CAA8C;IAC9C,OAAO,EAAE,OAAO,CAAC;IACjB,uCAAuC;IACvC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,UAAU,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC;6FACyF;IACzF,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;CACd;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,QAAQ,EAAE,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;IACnD,4DAA4D;IAC5D,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,6DAA6D;IAC7D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC;;yBAEqB;IACrB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,qBAAqB,GAAG,SAAS,CAAC"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the byte length of a string (UTF-8).
|
|
3
|
+
*/
|
|
4
|
+
export declare function byteSize(value: unknown): number;
|
|
5
|
+
/**
|
|
6
|
+
* Compute a percentile from a sorted array of numbers.
|
|
7
|
+
* Uses linear interpolation between the closest ranks.
|
|
8
|
+
*/
|
|
9
|
+
export declare function percentile(sorted: number[], p: number): number;
|
|
10
|
+
/**
|
|
11
|
+
* Insert a value into an already-sorted array (ascending), maintaining sort order.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sortedInsert(arr: number[], value: number): void;
|
|
14
|
+
//# sourceMappingURL=utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAI/C;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAY9D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAY/D"}
|