@listo-ai/mcp-observability 0.3.1 → 0.4.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 +34 -6
- package/dist/client.d.ts +6 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +13 -1
- package/dist/endpoints.d.ts.map +1 -1
- package/dist/endpoints.js +13 -0
- package/dist/index.d.ts +22 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +25 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -83,10 +83,19 @@ import { createTelemetryClient } from '@listo-ai/mcp-observability/client';
|
|
|
83
83
|
|
|
84
84
|
const client = createTelemetryClient({
|
|
85
85
|
endpoint: 'https://myapp.com/telemetry',
|
|
86
|
+
deviceId: localStorage.getItem('device_id') ?? undefined,
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
// Share the session ID across widget instances
|
|
90
|
+
const sessionId = client.getSessionId();
|
|
91
|
+
|
|
88
92
|
client.track('page_view', { path: '/home' });
|
|
89
|
-
client.
|
|
93
|
+
client.track('purchase', { amount: 99 }, 'conversion');
|
|
94
|
+
client.trackUi('button_click', {
|
|
95
|
+
action: 'click',
|
|
96
|
+
category: 'engagement',
|
|
97
|
+
widgetId: 'cta-1',
|
|
98
|
+
});
|
|
90
99
|
```
|
|
91
100
|
|
|
92
101
|
The client has no Node.js dependencies — it uses `fetch()`, `crypto.randomUUID()`, and `navigator.sendBeacon()`. See [Browser Client docs](./docs/client.md) for full API reference.
|
|
@@ -219,18 +228,32 @@ server.setRequestHandler(
|
|
|
219
228
|
);
|
|
220
229
|
```
|
|
221
230
|
|
|
222
|
-
### `observability.recordBusinessEvent(name,
|
|
231
|
+
### `observability.recordBusinessEvent(name, options?)`
|
|
223
232
|
|
|
224
233
|
Record custom business events for domain-specific analytics.
|
|
225
234
|
|
|
226
235
|
```typescript
|
|
227
236
|
observability.recordBusinessEvent('product_search', {
|
|
228
|
-
q: 'premium items',
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
237
|
+
properties: { q: 'premium items', results: 42 },
|
|
238
|
+
status: 'ok',
|
|
239
|
+
category: 'conversion',
|
|
240
|
+
sessionId: 'sess-abc',
|
|
241
|
+
deviceId: 'device-xyz',
|
|
242
|
+
tenantId: 'tenant-1',
|
|
243
|
+
});
|
|
232
244
|
```
|
|
233
245
|
|
|
246
|
+
Options:
|
|
247
|
+
|
|
248
|
+
| Field | Type | Description |
|
|
249
|
+
| --- | --- | --- |
|
|
250
|
+
| `properties` | `Record<string, unknown>?` | Arbitrary key-value metadata |
|
|
251
|
+
| `status` | `'ok' \| 'error'?` | Event status. Error events bypass sampling |
|
|
252
|
+
| `category` | `EventCategory?` | `'conversion'`, `'engagement'`, `'impression'`, `'navigation'`, or `'system'` |
|
|
253
|
+
| `sessionId` | `string?` | Session identifier for journey tracking |
|
|
254
|
+
| `deviceId` | `string?` | Device identifier for cross-session user identification |
|
|
255
|
+
| `tenantId` | `string?` | Tenant identifier for multi-tenant apps |
|
|
256
|
+
|
|
234
257
|
### `observability.recordUiEvent(event)`
|
|
235
258
|
|
|
236
259
|
Record UI interaction events from the browser.
|
|
@@ -239,6 +262,7 @@ Record UI interaction events from the browser.
|
|
|
239
262
|
observability.recordUiEvent({
|
|
240
263
|
name: 'cta_click',
|
|
241
264
|
action: 'click',
|
|
265
|
+
category: 'engagement',
|
|
242
266
|
widgetId: 'signup-btn',
|
|
243
267
|
properties: { itemId: 'i-123' },
|
|
244
268
|
sessionId: 'session-456',
|
|
@@ -400,6 +424,9 @@ Custom domain events via `recordBusinessEvent()`.
|
|
|
400
424
|
type: 'business_event',
|
|
401
425
|
name: 'product_search',
|
|
402
426
|
status: 'ok',
|
|
427
|
+
category: 'engagement',
|
|
428
|
+
sessionId: 'sess-abc',
|
|
429
|
+
tenantId: 'tenant-1',
|
|
403
430
|
properties: { q: 'premium', results: 42 },
|
|
404
431
|
}
|
|
405
432
|
```
|
|
@@ -413,6 +440,7 @@ Browser interaction events via `recordUiEvent()` or the `POST /telemetry/event`
|
|
|
413
440
|
type: 'ui_event',
|
|
414
441
|
name: 'cta_click',
|
|
415
442
|
action: 'click',
|
|
443
|
+
category: 'engagement',
|
|
416
444
|
sessionId: 'session-123',
|
|
417
445
|
properties: { itemId: 'i-456' },
|
|
418
446
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface TelemetryClientOptions {
|
|
2
2
|
endpoint: string;
|
|
3
3
|
sessionId?: string;
|
|
4
|
+
deviceId?: string;
|
|
4
5
|
tenantId?: string;
|
|
5
6
|
locale?: string;
|
|
6
7
|
batchSize?: number;
|
|
@@ -11,6 +12,7 @@ export interface TelemetryClientOptions {
|
|
|
11
12
|
export declare class TelemetryClient {
|
|
12
13
|
private readonly endpoint;
|
|
13
14
|
private readonly sessionId;
|
|
15
|
+
private readonly deviceId?;
|
|
14
16
|
private readonly batchSize;
|
|
15
17
|
private readonly flushIntervalMs;
|
|
16
18
|
private readonly onError;
|
|
@@ -20,9 +22,12 @@ export declare class TelemetryClient {
|
|
|
20
22
|
private context;
|
|
21
23
|
private destroyed;
|
|
22
24
|
constructor(options: TelemetryClientOptions);
|
|
23
|
-
|
|
25
|
+
getSessionId(): string;
|
|
26
|
+
getDeviceId(): string | undefined;
|
|
27
|
+
track(name: string, properties?: Record<string, unknown>, category?: string): void;
|
|
24
28
|
trackUi(name: string, options: {
|
|
25
29
|
action?: string;
|
|
30
|
+
category?: string;
|
|
26
31
|
widgetId?: string;
|
|
27
32
|
toolName?: string;
|
|
28
33
|
properties?: Record<string, unknown>;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAgBD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IACzC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAU;IAChC,OAAO,CAAC,KAAK,CAAwB;IACrC,OAAO,CAAC,KAAK,CAA+C;IAC5D,OAAO,CAAC,OAAO,CAAyC;IACxD,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,EAAE,sBAAsB;IAuB3C,YAAY,IAAI,MAAM;IAItB,WAAW,IAAI,MAAM,GAAG,SAAS;IAIjC,KAAK,CACH,IAAI,EAAE,MAAM,EACZ,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpC,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI;IAcP,OAAO,CACL,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;QACP,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACtC,GACA,IAAI;IAiBP,UAAU,CAAC,GAAG,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI;IAK7D,KAAK,IAAI,IAAI;IAMb,OAAO,IAAI,IAAI;IAef,OAAO,CAAC,OAAO;IAOf,OAAO,CAAC,IAAI;IA8BZ,OAAO,CAAC,sBAAsB,CAI5B;CACH;AAED,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,sBAAsB,GAC9B,eAAe,CAEjB"}
|
package/dist/client.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export class TelemetryClient {
|
|
2
2
|
endpoint;
|
|
3
3
|
sessionId;
|
|
4
|
+
deviceId;
|
|
4
5
|
batchSize;
|
|
5
6
|
flushIntervalMs;
|
|
6
7
|
onError;
|
|
@@ -12,6 +13,7 @@ export class TelemetryClient {
|
|
|
12
13
|
constructor(options) {
|
|
13
14
|
this.endpoint = options.endpoint.replace(/\/+$/, '');
|
|
14
15
|
this.sessionId = options.sessionId ?? globalThis.crypto.randomUUID();
|
|
16
|
+
this.deviceId = options.deviceId;
|
|
15
17
|
this.batchSize = options.batchSize ?? 20;
|
|
16
18
|
this.flushIntervalMs = options.flushIntervalMs ?? 5000;
|
|
17
19
|
this.onError = options.onError ?? (() => { });
|
|
@@ -25,15 +27,23 @@ export class TelemetryClient {
|
|
|
25
27
|
globalThis.document.addEventListener('visibilitychange', this.handleVisibilityChange);
|
|
26
28
|
}
|
|
27
29
|
}
|
|
28
|
-
|
|
30
|
+
getSessionId() {
|
|
31
|
+
return this.sessionId;
|
|
32
|
+
}
|
|
33
|
+
getDeviceId() {
|
|
34
|
+
return this.deviceId;
|
|
35
|
+
}
|
|
36
|
+
track(name, properties, category) {
|
|
29
37
|
if (this.destroyed)
|
|
30
38
|
return;
|
|
31
39
|
this.enqueue({
|
|
32
40
|
name,
|
|
33
41
|
timestamp: Date.now(),
|
|
34
42
|
sessionId: this.sessionId,
|
|
43
|
+
deviceId: this.deviceId,
|
|
35
44
|
tenantId: this.context.tenantId,
|
|
36
45
|
locale: this.context.locale,
|
|
46
|
+
category,
|
|
37
47
|
properties,
|
|
38
48
|
});
|
|
39
49
|
}
|
|
@@ -44,9 +54,11 @@ export class TelemetryClient {
|
|
|
44
54
|
name,
|
|
45
55
|
timestamp: Date.now(),
|
|
46
56
|
sessionId: this.sessionId,
|
|
57
|
+
deviceId: this.deviceId,
|
|
47
58
|
tenantId: this.context.tenantId,
|
|
48
59
|
locale: this.context.locale,
|
|
49
60
|
action: options.action,
|
|
61
|
+
category: options.category,
|
|
50
62
|
widgetId: options.widgetId,
|
|
51
63
|
toolName: options.toolName,
|
|
52
64
|
properties: options.properties,
|
package/dist/endpoints.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAEjE,QAAA,MAAM,kBAAkB,eAAsC,CAAC;AAC/D,QAAA,MAAM,iBAAiB,eAAsC,CAAC;AAe9D,iBAAS,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAEjD;
|
|
1
|
+
{"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC/D,OAAO,KAAK,EAAE,gBAAgB,EAAgB,MAAM,YAAY,CAAC;AAEjE,QAAA,MAAM,kBAAkB,eAAsC,CAAC;AAC/D,QAAA,MAAM,iBAAiB,eAAsC,CAAC;AAe9D,iBAAS,SAAS,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI,CAEjD;AA4CD;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,gBAAgB,IAChD,KAAK,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,mBAW9D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AAEH,wBAAgB,qBAAqB,CAAC,aAAa,EAAE,GAAG,OAySvD;AAED,OAAO,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,SAAS,EAAE,CAAC"}
|
package/dist/endpoints.js
CHANGED
|
@@ -17,13 +17,22 @@ function setGlobal(key, value) {
|
|
|
17
17
|
const UI_EVENT_FIELDS = new Set([
|
|
18
18
|
'name',
|
|
19
19
|
'action',
|
|
20
|
+
'category',
|
|
20
21
|
'widgetId',
|
|
21
22
|
'toolName',
|
|
22
23
|
'sessionId',
|
|
24
|
+
'deviceId',
|
|
23
25
|
'tenantId',
|
|
24
26
|
'locale',
|
|
25
27
|
'properties',
|
|
26
28
|
]);
|
|
29
|
+
const VALID_CATEGORIES = new Set([
|
|
30
|
+
'conversion',
|
|
31
|
+
'engagement',
|
|
32
|
+
'impression',
|
|
33
|
+
'navigation',
|
|
34
|
+
'system',
|
|
35
|
+
]);
|
|
27
36
|
const MAX_EVENT_BODY_SIZE = 8192;
|
|
28
37
|
function validateUiEventBody(body) {
|
|
29
38
|
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
@@ -31,6 +40,10 @@ function validateUiEventBody(body) {
|
|
|
31
40
|
const raw = body;
|
|
32
41
|
if (typeof raw.name !== 'string' || raw.name.length === 0)
|
|
33
42
|
return null;
|
|
43
|
+
if (raw.category !== undefined &&
|
|
44
|
+
(typeof raw.category !== 'string' || !VALID_CATEGORIES.has(raw.category))) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
34
47
|
const cleaned = {};
|
|
35
48
|
for (const key of UI_EVENT_FIELDS) {
|
|
36
49
|
if (key in raw) {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
2
|
export type EventStatus = 'ok' | 'error';
|
|
3
|
+
export type EventCategory = 'conversion' | 'engagement' | 'impression' | 'navigation' | 'system';
|
|
3
4
|
export type HttpRequestEvent = {
|
|
4
5
|
type: 'http_request';
|
|
5
6
|
timestamp: number;
|
|
@@ -63,6 +64,10 @@ export type BusinessEvent = {
|
|
|
63
64
|
environment?: string;
|
|
64
65
|
name: string;
|
|
65
66
|
status?: EventStatus;
|
|
67
|
+
category?: EventCategory;
|
|
68
|
+
sessionId?: string;
|
|
69
|
+
deviceId?: string;
|
|
70
|
+
tenantId?: string;
|
|
66
71
|
properties?: Record<string, unknown>;
|
|
67
72
|
};
|
|
68
73
|
export type UiEvent = {
|
|
@@ -73,9 +78,11 @@ export type UiEvent = {
|
|
|
73
78
|
environment?: string;
|
|
74
79
|
name: string;
|
|
75
80
|
action?: string;
|
|
81
|
+
category?: EventCategory;
|
|
76
82
|
widgetId?: string;
|
|
77
83
|
toolName?: string;
|
|
78
84
|
sessionId?: string;
|
|
85
|
+
deviceId?: string;
|
|
79
86
|
tenantId?: string;
|
|
80
87
|
locale?: string;
|
|
81
88
|
properties?: Record<string, unknown>;
|
|
@@ -200,6 +207,7 @@ export declare class InMemorySink implements EventSink {
|
|
|
200
207
|
byName: Record<string, number>;
|
|
201
208
|
byAction: Record<string, number>;
|
|
202
209
|
byWidget: Record<string, number>;
|
|
210
|
+
byCategory: Record<string, number>;
|
|
203
211
|
total: number;
|
|
204
212
|
};
|
|
205
213
|
business: {
|
|
@@ -208,6 +216,11 @@ export declare class InMemorySink implements EventSink {
|
|
|
208
216
|
ok: number;
|
|
209
217
|
error: number;
|
|
210
218
|
}>;
|
|
219
|
+
byCategory: Record<string, {
|
|
220
|
+
count: number;
|
|
221
|
+
ok: number;
|
|
222
|
+
error: number;
|
|
223
|
+
}>;
|
|
211
224
|
};
|
|
212
225
|
};
|
|
213
226
|
}
|
|
@@ -215,6 +228,14 @@ export declare function createConsoleSink(options?: {
|
|
|
215
228
|
logSuccess?: boolean;
|
|
216
229
|
}): EventSink;
|
|
217
230
|
export declare function combineSinks(...sinks: EventSink[]): EventSink;
|
|
231
|
+
export type BusinessEventOptions = {
|
|
232
|
+
properties?: Record<string, unknown>;
|
|
233
|
+
status?: EventStatus;
|
|
234
|
+
category?: EventCategory;
|
|
235
|
+
sessionId?: string;
|
|
236
|
+
deviceId?: string;
|
|
237
|
+
tenantId?: string;
|
|
238
|
+
};
|
|
218
239
|
export declare class McpObservability {
|
|
219
240
|
private readonly serviceName;
|
|
220
241
|
private readonly serviceVersion?;
|
|
@@ -229,7 +250,7 @@ export declare class McpObservability {
|
|
|
229
250
|
trackHttpRequest(req: IncomingMessage, res: ServerResponse, handler: (ctx: HttpTrackingContext) => Promise<void>): Promise<void>;
|
|
230
251
|
wrapMcpHandler<TRequest, TResponse>(requestKind: string, handler: (request: TRequest) => Promise<TResponse> | TResponse, context?: (request: TRequest) => McpTrackingContext): (request: TRequest) => Promise<TResponse>;
|
|
231
252
|
recordSession(event: Omit<McpSessionEvent, 'timestamp' | 'service'>): void;
|
|
232
|
-
recordBusinessEvent(name: string,
|
|
253
|
+
recordBusinessEvent(name: string, options?: BusinessEventOptions): void;
|
|
233
254
|
private baseMcpFields;
|
|
234
255
|
private sanitizePayload;
|
|
235
256
|
private safeEmit;
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC;AAEzC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,WAAW,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,gBAAgB,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,gBAAgB,GAChB,eAAe,GACf,eAAe,GACf,aAAa,GACb,OAAO,CAAC;AAEZ,MAAM,MAAM,SAAS,GAAG;IACtB,IAAI,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3D,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,EAAE,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,MAAM,GAAG,SAAS,CAAC;CAC/D,CAAC;AAEF,KAAK,mBAAmB,GAAG;IACzB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC/C,CAAC;AAEF,KAAK,kBAAkB,GAAG;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,KAAK,OAAO,CAAC;CAC9C,CAAC;AAEF,qBAAa,YAAa,YAAW,SAAS;IAC5C,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAIxC,IAAI,CAAC,KAAK,EAAE,kBAAkB;IAO9B,SAAS,IAAI,kBAAkB,EAAE;IAIjC,UAAU
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -273,7 +273,7 @@ export class McpObservability {
|
|
|
273
273
|
};
|
|
274
274
|
this.safeEmit(enriched);
|
|
275
275
|
}
|
|
276
|
-
recordBusinessEvent(name,
|
|
276
|
+
recordBusinessEvent(name, options) {
|
|
277
277
|
const event = {
|
|
278
278
|
type: 'business_event',
|
|
279
279
|
timestamp: Date.now(),
|
|
@@ -281,10 +281,14 @@ export class McpObservability {
|
|
|
281
281
|
serviceVersion: this.serviceVersion,
|
|
282
282
|
environment: this.environment,
|
|
283
283
|
name,
|
|
284
|
-
properties: properties
|
|
285
|
-
? this.sanitizePayload(properties)
|
|
284
|
+
properties: options?.properties
|
|
285
|
+
? this.sanitizePayload(options.properties)
|
|
286
286
|
: undefined,
|
|
287
|
-
status,
|
|
287
|
+
status: options?.status,
|
|
288
|
+
category: options?.category,
|
|
289
|
+
sessionId: options?.sessionId,
|
|
290
|
+
deviceId: options?.deviceId,
|
|
291
|
+
tenantId: options?.tenantId,
|
|
288
292
|
};
|
|
289
293
|
this.safeEmit(event);
|
|
290
294
|
}
|
|
@@ -432,6 +436,7 @@ function summarizeUiEvents(events) {
|
|
|
432
436
|
const byName = {};
|
|
433
437
|
const byAction = {};
|
|
434
438
|
const byWidget = {};
|
|
439
|
+
const byCategory = {};
|
|
435
440
|
for (const event of events) {
|
|
436
441
|
const nameKey = event.name || 'unknown';
|
|
437
442
|
const actionKey = event.action || 'unspecified';
|
|
@@ -439,11 +444,15 @@ function summarizeUiEvents(events) {
|
|
|
439
444
|
byName[nameKey] = (byName[nameKey] ?? 0) + 1;
|
|
440
445
|
byAction[actionKey] = (byAction[actionKey] ?? 0) + 1;
|
|
441
446
|
byWidget[widgetKey] = (byWidget[widgetKey] ?? 0) + 1;
|
|
447
|
+
if (event.category) {
|
|
448
|
+
byCategory[event.category] = (byCategory[event.category] ?? 0) + 1;
|
|
449
|
+
}
|
|
442
450
|
}
|
|
443
|
-
return { byName, byAction, byWidget, total: events.length };
|
|
451
|
+
return { byName, byAction, byWidget, byCategory, total: events.length };
|
|
444
452
|
}
|
|
445
453
|
function summarizeBusinessEvents(events) {
|
|
446
454
|
const byName = {};
|
|
455
|
+
const byCategory = {};
|
|
447
456
|
for (const event of events) {
|
|
448
457
|
const key = event.name || 'unknown';
|
|
449
458
|
if (!byName[key]) {
|
|
@@ -454,8 +463,18 @@ function summarizeBusinessEvents(events) {
|
|
|
454
463
|
byName[key].error += 1;
|
|
455
464
|
if (event.status === 'ok')
|
|
456
465
|
byName[key].ok += 1;
|
|
466
|
+
if (event.category) {
|
|
467
|
+
if (!byCategory[event.category]) {
|
|
468
|
+
byCategory[event.category] = { count: 0, ok: 0, error: 0 };
|
|
469
|
+
}
|
|
470
|
+
byCategory[event.category].count += 1;
|
|
471
|
+
if (event.status === 'error')
|
|
472
|
+
byCategory[event.category].error += 1;
|
|
473
|
+
if (event.status === 'ok')
|
|
474
|
+
byCategory[event.category].ok += 1;
|
|
475
|
+
}
|
|
457
476
|
}
|
|
458
|
-
return { byName };
|
|
477
|
+
return { byName, byCategory };
|
|
459
478
|
}
|
|
460
479
|
function truncateString(value, max = 240) {
|
|
461
480
|
if (value.length <= max)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@listo-ai/mcp-observability",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Lightweight telemetry SDK for MCP servers and web applications. Captures HTTP requests, MCP tool invocations, business events, and UI interactions with built-in payload sanitization.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|