@listo-ai/mcp-observability 0.2.0 → 0.3.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 +73 -48
- package/dist/client.d.ts +41 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +123 -0
- package/dist/easy-setup.d.ts +8 -4
- package/dist/easy-setup.d.ts.map +1 -1
- package/dist/easy-setup.js +16 -13
- package/dist/endpoints.d.ts +6 -2
- package/dist/endpoints.d.ts.map +1 -1
- package/dist/endpoints.js +45 -14
- package/dist/index.d.ts +0 -64
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -317
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -4,13 +4,14 @@ Lightweight telemetry SDK for MCP servers and web applications. Captures HTTP re
|
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
This SDK provides comprehensive observability for [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers and Express.js applications. It
|
|
7
|
+
This SDK provides comprehensive observability for [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers and Express.js applications. It supports centralized analytics via a remote sink, and includes a local development dashboard for real-time metrics.
|
|
8
8
|
|
|
9
9
|
**Key features:**
|
|
10
10
|
|
|
11
11
|
- **Easy setup** -- Get started in 4 lines of code with `createMcpObservabilityEasy()`
|
|
12
12
|
- **Local dashboard** -- Real-time metrics dashboard during development at `/telemetry/dashboard`
|
|
13
|
-
- **
|
|
13
|
+
- **Browser client** -- Reusable `TelemetryClient` for browser apps with batching and `sendBeacon` fallback
|
|
14
|
+
- **Centralized analytics** -- Send telemetry data to a remote endpoint in production
|
|
14
15
|
- **Automatic tracking** -- HTTP requests via Express middleware, MCP tool invocations via handler wrapper
|
|
15
16
|
- **Payload sanitization** -- Built-in redaction of sensitive keys (`password`, `token`, `apiKey`, `secret`, `authorization`)
|
|
16
17
|
- **Sampling** -- Configurable sampling rates with guaranteed capture of errors and session events
|
|
@@ -20,6 +21,7 @@ This SDK provides comprehensive observability for [Model Context Protocol (MCP)]
|
|
|
20
21
|
|
|
21
22
|
- [Installation](#installation)
|
|
22
23
|
- [Quick Start](#quick-start)
|
|
24
|
+
- [Browser Client](#browser-client)
|
|
23
25
|
- [How It Works](#how-it-works)
|
|
24
26
|
- [API Reference](#api-reference)
|
|
25
27
|
- [Integration Guide](#integration-guide)
|
|
@@ -56,14 +58,14 @@ const observability = createMcpObservabilityEasy({
|
|
|
56
58
|
```typescript
|
|
57
59
|
import { createTelemetryRouter } from '@listo-ai/mcp-observability';
|
|
58
60
|
|
|
59
|
-
app.use('/telemetry', createTelemetryRouter());
|
|
61
|
+
app.use('/telemetry', createTelemetryRouter(express));
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
### 3. Set environment variables
|
|
64
|
+
### 3. Set environment variables (optional, for remote telemetry)
|
|
63
65
|
|
|
64
66
|
```bash
|
|
65
|
-
|
|
66
|
-
|
|
67
|
+
LISTO_API_URL=https://your-api.example.com
|
|
68
|
+
LISTO_API_KEY=your_api_key
|
|
67
69
|
```
|
|
68
70
|
|
|
69
71
|
### 4. Access the local dashboard
|
|
@@ -72,6 +74,23 @@ INSIGHTS_API_KEY=fuse_xxxxxxxxxxxxx
|
|
|
72
74
|
http://localhost:3000/telemetry/dashboard
|
|
73
75
|
```
|
|
74
76
|
|
|
77
|
+
## Browser Client
|
|
78
|
+
|
|
79
|
+
For browser applications, use the lightweight client module with automatic batching and session management:
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createTelemetryClient } from '@listo-ai/mcp-observability/client';
|
|
83
|
+
|
|
84
|
+
const client = createTelemetryClient({
|
|
85
|
+
endpoint: 'https://myapp.com/telemetry',
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
client.track('page_view', { path: '/home' });
|
|
89
|
+
client.trackUi('button_click', { action: 'click', widgetId: 'cta-1' });
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
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.
|
|
93
|
+
|
|
75
94
|
## How It Works
|
|
76
95
|
|
|
77
96
|
### Environment-Based Auto-Configuration
|
|
@@ -97,7 +116,7 @@ Observability SDK (captures events)
|
|
|
97
116
|
|
|
|
98
117
|
+---> Console Sink (dev only, errors)
|
|
99
118
|
+---> InMemory Sink (local dashboard)
|
|
100
|
-
+---> Remote Sink (
|
|
119
|
+
+---> Remote Sink (remote endpoint, batched)
|
|
101
120
|
```
|
|
102
121
|
|
|
103
122
|
### Sink Architecture
|
|
@@ -105,7 +124,7 @@ Observability SDK (captures events)
|
|
|
105
124
|
Events flow through configurable **sinks** -- decoupled consumers that process telemetry data:
|
|
106
125
|
|
|
107
126
|
- **`InMemorySink`** -- Circular buffer (default 2000 events) powering the local dashboard
|
|
108
|
-
- **`RemoteSink`** -- Batches events and sends them to
|
|
127
|
+
- **`RemoteSink`** -- Batches events and sends them to a remote endpoint with exponential backoff retries
|
|
109
128
|
- **`ConsoleSink`** -- Logs errors to stdout
|
|
110
129
|
- **`combineSinks()`** -- Routes events to multiple sinks simultaneously
|
|
111
130
|
|
|
@@ -120,8 +139,8 @@ interface EasySetupConfig {
|
|
|
120
139
|
serviceName: string;
|
|
121
140
|
serviceVersion?: string; // default: "1.0.0"
|
|
122
141
|
environment?: "dev" | "staging" | "production";
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
listoApiUrl?: string; // default: process.env.LISTO_API_URL
|
|
143
|
+
listoApiKey?: string; // default: process.env.LISTO_API_KEY
|
|
125
144
|
enableLocalDashboard?: boolean; // default: true in dev, false in prod
|
|
126
145
|
sampleRate?: number; // default: 1 in dev, 0.1 in prod
|
|
127
146
|
}
|
|
@@ -129,7 +148,7 @@ interface EasySetupConfig {
|
|
|
129
148
|
|
|
130
149
|
```typescript
|
|
131
150
|
const observability = createMcpObservabilityEasy({
|
|
132
|
-
serviceName: '
|
|
151
|
+
serviceName: 'my-mcp-server',
|
|
133
152
|
serviceVersion: '0.1.0',
|
|
134
153
|
enableLocalDashboard: true,
|
|
135
154
|
});
|
|
@@ -152,7 +171,7 @@ interface ObservabilityOptions {
|
|
|
152
171
|
}
|
|
153
172
|
```
|
|
154
173
|
|
|
155
|
-
### `createTelemetryRouter()`
|
|
174
|
+
### `createTelemetryRouter(express)`
|
|
156
175
|
|
|
157
176
|
Express router providing telemetry endpoints:
|
|
158
177
|
|
|
@@ -160,12 +179,13 @@ Express router providing telemetry endpoints:
|
|
|
160
179
|
| --- | --- | --- |
|
|
161
180
|
| `/` | GET | JSON metrics data |
|
|
162
181
|
| `/dashboard` | GET | HTML dashboard with auto-refresh |
|
|
163
|
-
| `/event` | POST | UI event ingestion |
|
|
182
|
+
| `/event` | POST | Single UI event ingestion |
|
|
183
|
+
| `/events` | POST | Batch UI event ingestion (max 100) |
|
|
164
184
|
|
|
165
185
|
```typescript
|
|
166
186
|
import { createTelemetryRouter } from '@listo-ai/mcp-observability';
|
|
167
187
|
|
|
168
|
-
app.use('/telemetry', createTelemetryRouter());
|
|
188
|
+
app.use('/telemetry', createTelemetryRouter(express));
|
|
169
189
|
// Dashboard: http://localhost:3000/telemetry/dashboard
|
|
170
190
|
```
|
|
171
191
|
|
|
@@ -204,10 +224,10 @@ server.setRequestHandler(
|
|
|
204
224
|
Record custom business events for domain-specific analytics.
|
|
205
225
|
|
|
206
226
|
```typescript
|
|
207
|
-
observability.recordBusinessEvent('
|
|
208
|
-
q: '
|
|
209
|
-
|
|
210
|
-
|
|
227
|
+
observability.recordBusinessEvent('product_search', {
|
|
228
|
+
q: 'premium items',
|
|
229
|
+
category: 'electronics',
|
|
230
|
+
results: 42,
|
|
211
231
|
}, 'ok');
|
|
212
232
|
```
|
|
213
233
|
|
|
@@ -217,8 +237,10 @@ Record UI interaction events from the browser.
|
|
|
217
237
|
|
|
218
238
|
```typescript
|
|
219
239
|
observability.recordUiEvent({
|
|
220
|
-
name: '
|
|
221
|
-
|
|
240
|
+
name: 'cta_click',
|
|
241
|
+
action: 'click',
|
|
242
|
+
widgetId: 'signup-btn',
|
|
243
|
+
properties: { itemId: 'i-123' },
|
|
222
244
|
sessionId: 'session-456',
|
|
223
245
|
tenantId: 'tenant-789',
|
|
224
246
|
});
|
|
@@ -244,8 +266,8 @@ Standalone remote sink for custom configurations.
|
|
|
244
266
|
import { RemoteSink } from '@listo-ai/mcp-observability';
|
|
245
267
|
|
|
246
268
|
const sink = new RemoteSink({
|
|
247
|
-
endpoint: 'https://api.
|
|
248
|
-
apiKey: '
|
|
269
|
+
endpoint: 'https://your-api.example.com/v1/events/batch',
|
|
270
|
+
apiKey: 'your_api_key',
|
|
249
271
|
batchSize: 50, // events per batch
|
|
250
272
|
flushIntervalMs: 5000, // flush every 5s
|
|
251
273
|
maxRetries: 3, // retry with exponential backoff
|
|
@@ -275,11 +297,11 @@ const observability = createMcpObservabilityEasy({
|
|
|
275
297
|
app.use(expressTelemetry(observability));
|
|
276
298
|
|
|
277
299
|
// Telemetry endpoints
|
|
278
|
-
app.use('/telemetry', createTelemetryRouter());
|
|
300
|
+
app.use('/telemetry', createTelemetryRouter(express));
|
|
279
301
|
|
|
280
|
-
app.get('/
|
|
302
|
+
app.get('/products', (req, res) => {
|
|
281
303
|
// Automatically tracked
|
|
282
|
-
res.json({
|
|
304
|
+
res.json({ products: [] });
|
|
283
305
|
});
|
|
284
306
|
|
|
285
307
|
app.listen(3000);
|
|
@@ -297,14 +319,14 @@ import express from 'express';
|
|
|
297
319
|
|
|
298
320
|
const app = express();
|
|
299
321
|
const observability = createMcpObservabilityEasy({
|
|
300
|
-
serviceName: '
|
|
322
|
+
serviceName: 'my-mcp-server',
|
|
301
323
|
serviceVersion: '0.1.0',
|
|
302
324
|
});
|
|
303
325
|
|
|
304
|
-
app.use('/telemetry', createTelemetryRouter());
|
|
326
|
+
app.use('/telemetry', createTelemetryRouter(express));
|
|
305
327
|
|
|
306
328
|
const server = new Server({
|
|
307
|
-
name: '
|
|
329
|
+
name: 'my-mcp-server',
|
|
308
330
|
version: '0.1.0',
|
|
309
331
|
});
|
|
310
332
|
|
|
@@ -332,8 +354,8 @@ Automatically captured via `expressTelemetry()` middleware.
|
|
|
332
354
|
{
|
|
333
355
|
type: 'http_request',
|
|
334
356
|
method: 'GET',
|
|
335
|
-
path: '/
|
|
336
|
-
route: '/
|
|
357
|
+
path: '/products',
|
|
358
|
+
route: '/products/:id',
|
|
337
359
|
statusCode: 200,
|
|
338
360
|
status: 'ok',
|
|
339
361
|
latencyMs: 45.2,
|
|
@@ -349,7 +371,7 @@ Captured via `wrapMcpHandler()`.
|
|
|
349
371
|
{
|
|
350
372
|
type: 'mcp_request',
|
|
351
373
|
requestKind: 'CallTool',
|
|
352
|
-
toolName: '
|
|
374
|
+
toolName: 'search_products',
|
|
353
375
|
status: 'ok',
|
|
354
376
|
latencyMs: 234.5,
|
|
355
377
|
inputBytes: 156,
|
|
@@ -376,9 +398,9 @@ Custom domain events via `recordBusinessEvent()`.
|
|
|
376
398
|
```typescript
|
|
377
399
|
{
|
|
378
400
|
type: 'business_event',
|
|
379
|
-
name: '
|
|
401
|
+
name: 'product_search',
|
|
380
402
|
status: 'ok',
|
|
381
|
-
properties: { q: '
|
|
403
|
+
properties: { q: 'premium', results: 42 },
|
|
382
404
|
}
|
|
383
405
|
```
|
|
384
406
|
|
|
@@ -389,9 +411,10 @@ Browser interaction events via `recordUiEvent()` or the `POST /telemetry/event`
|
|
|
389
411
|
```typescript
|
|
390
412
|
{
|
|
391
413
|
type: 'ui_event',
|
|
392
|
-
name: '
|
|
414
|
+
name: 'cta_click',
|
|
415
|
+
action: 'click',
|
|
393
416
|
sessionId: 'session-123',
|
|
394
|
-
properties: {
|
|
417
|
+
properties: { itemId: 'i-456' },
|
|
395
418
|
}
|
|
396
419
|
```
|
|
397
420
|
|
|
@@ -399,8 +422,8 @@ Browser interaction events via `recordUiEvent()` or the `POST /telemetry/event`
|
|
|
399
422
|
|
|
400
423
|
| Variable | Required | Description |
|
|
401
424
|
| --- | --- | --- |
|
|
402
|
-
| `
|
|
403
|
-
| `
|
|
425
|
+
| `LISTO_API_URL` | For remote | Listo API endpoint |
|
|
426
|
+
| `LISTO_API_KEY` | For remote | API key for authentication |
|
|
404
427
|
| `NODE_ENV` | No | `development` / `staging` / `production` -- controls defaults |
|
|
405
428
|
| `TELEMETRY_SAMPLE_RATE` | No | Override sampling percentage (0.0 - 1.0) |
|
|
406
429
|
| `TELEMETRY_CAPTURE_PAYLOADS` | No | Enable/disable payload capture |
|
|
@@ -411,7 +434,7 @@ Browser interaction events via `recordUiEvent()` or the `POST /telemetry/event`
|
|
|
411
434
|
|
|
412
435
|
The SDK automatically redacts sensitive keys from all captured payloads. Redaction is applied recursively up to 6 levels deep.
|
|
413
436
|
|
|
414
|
-
**Default redacted keys:** `password`, `token`, `apiKey`, `secret`, `authorization`
|
|
437
|
+
**Default redacted keys:** `password`, `token`, `apiKey`, `secret`, `authorization`, `userId`, `userLocation`, `email`, `userEmail`, `phone`, `phoneNumber`
|
|
415
438
|
|
|
416
439
|
**Custom redaction:**
|
|
417
440
|
|
|
@@ -457,6 +480,7 @@ Full Amplitude-style documentation is available in the [`docs/`](./docs/) direct
|
|
|
457
480
|
| [Sessions](./docs/sessions.md) | `recordSession()`, always-capture behavior |
|
|
458
481
|
| [Business Events](./docs/business-events.md) | `recordBusinessEvent()` |
|
|
459
482
|
| [UI Events](./docs/ui-events.md) | `recordUiEvent()` + POST endpoint |
|
|
483
|
+
| [Browser Client](./docs/client.md) | `TelemetryClient` for browser apps |
|
|
460
484
|
| [Sinks](./docs/sinks.md) | InMemorySink, RemoteSink, ConsoleSink, combineSinks |
|
|
461
485
|
| [Endpoints](./docs/endpoints.md) | Dashboard, JSON API, event ingestion |
|
|
462
486
|
| [Easy Setup](./docs/easy-setup.md) | `createMcpObservabilityEasy()` deep-dive |
|
|
@@ -522,7 +546,8 @@ mcp-observability/
|
|
|
522
546
|
│ └── publish.yml # Publish to npm on release
|
|
523
547
|
├── docs/ # Comprehensive SDK documentation
|
|
524
548
|
├── src/
|
|
525
|
-
│ ├── index.ts # Core SDK: types, classes,
|
|
549
|
+
│ ├── index.ts # Core SDK: types, classes, metrics
|
|
550
|
+
│ ├── client.ts # Browser telemetry client
|
|
526
551
|
│ ├── endpoints.ts # Express router and middleware
|
|
527
552
|
│ ├── easy-setup.ts # Simplified configuration factory
|
|
528
553
|
│ └── remote-sink.ts # Remote event batching and transmission
|
|
@@ -539,10 +564,11 @@ mcp-observability/
|
|
|
539
564
|
|
|
540
565
|
| File | Description |
|
|
541
566
|
| --- | --- |
|
|
542
|
-
| `src/index.ts` | Core SDK -- event types, `McpObservability` class, `InMemorySink`, sampling, sanitization
|
|
543
|
-
| `src/
|
|
567
|
+
| `src/index.ts` | Core SDK -- event types, `McpObservability` class, `InMemorySink`, sampling, sanitization |
|
|
568
|
+
| `src/client.ts` | Browser telemetry client -- `TelemetryClient` class with batching, session management, `sendBeacon` fallback |
|
|
569
|
+
| `src/endpoints.ts` | `expressTelemetry()` middleware and `createTelemetryRouter(express)` for JSON metrics, HTML dashboard, and event ingestion |
|
|
544
570
|
| `src/easy-setup.ts` | `createMcpObservabilityEasy()` -- auto-configures sinks and sampling based on environment |
|
|
545
|
-
| `src/remote-sink.ts` | `RemoteSink` class -- batches events and sends to
|
|
571
|
+
| `src/remote-sink.ts` | `RemoteSink` class -- batches events and sends to remote API with retry logic |
|
|
546
572
|
|
|
547
573
|
## CI/CD and Publishing
|
|
548
574
|
|
|
@@ -583,14 +609,13 @@ The tag, npm publish, and GitHub Release are all handled automatically by CI.
|
|
|
583
609
|
|
|
584
610
|
- Ensure `enableLocalDashboard: true` is set (default in dev)
|
|
585
611
|
- Visit `http://localhost:PORT/telemetry/dashboard`
|
|
586
|
-
- Confirm `createTelemetryRouter()` is mounted: `app.use('/telemetry', createTelemetryRouter())`
|
|
612
|
+
- Confirm `createTelemetryRouter(express)` is mounted: `app.use('/telemetry', createTelemetryRouter(express))`
|
|
587
613
|
|
|
588
|
-
### Data not reaching
|
|
614
|
+
### Data not reaching remote endpoint
|
|
589
615
|
|
|
590
|
-
- Verify `
|
|
591
|
-
- Check `
|
|
592
|
-
- Look for
|
|
593
|
-
- Generate a new API key at https://app.listoai.co/settings
|
|
616
|
+
- Verify `LISTO_API_KEY` is set and valid
|
|
617
|
+
- Check `LISTO_API_URL` is correct
|
|
618
|
+
- Look for warnings in server logs
|
|
594
619
|
|
|
595
620
|
### Performance overhead
|
|
596
621
|
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
export interface TelemetryClientOptions {
|
|
2
|
+
endpoint: string;
|
|
3
|
+
sessionId?: string;
|
|
4
|
+
tenantId?: string;
|
|
5
|
+
locale?: string;
|
|
6
|
+
batchSize?: number;
|
|
7
|
+
flushIntervalMs?: number;
|
|
8
|
+
onError?: (error: unknown) => void;
|
|
9
|
+
debug?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare class TelemetryClient {
|
|
12
|
+
private readonly endpoint;
|
|
13
|
+
private readonly sessionId;
|
|
14
|
+
private readonly batchSize;
|
|
15
|
+
private readonly flushIntervalMs;
|
|
16
|
+
private readonly onError;
|
|
17
|
+
private readonly debug;
|
|
18
|
+
private queue;
|
|
19
|
+
private timer;
|
|
20
|
+
private context;
|
|
21
|
+
private destroyed;
|
|
22
|
+
constructor(options: TelemetryClientOptions);
|
|
23
|
+
track(name: string, properties?: Record<string, unknown>): void;
|
|
24
|
+
trackUi(name: string, options: {
|
|
25
|
+
action?: string;
|
|
26
|
+
widgetId?: string;
|
|
27
|
+
toolName?: string;
|
|
28
|
+
properties?: Record<string, unknown>;
|
|
29
|
+
}): void;
|
|
30
|
+
setContext(ctx: {
|
|
31
|
+
tenantId?: string;
|
|
32
|
+
locale?: string;
|
|
33
|
+
}): void;
|
|
34
|
+
flush(): void;
|
|
35
|
+
destroy(): void;
|
|
36
|
+
private enqueue;
|
|
37
|
+
private send;
|
|
38
|
+
private handleVisibilityChange;
|
|
39
|
+
}
|
|
40
|
+
export declare function createTelemetryClient(options: TelemetryClientOptions): TelemetryClient;
|
|
41
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +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;AAcD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,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;IAsB3C,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI;IAY/D,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,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;KACtC,GACA,IAAI;IAeP,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
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
export class TelemetryClient {
|
|
2
|
+
endpoint;
|
|
3
|
+
sessionId;
|
|
4
|
+
batchSize;
|
|
5
|
+
flushIntervalMs;
|
|
6
|
+
onError;
|
|
7
|
+
debug;
|
|
8
|
+
queue = [];
|
|
9
|
+
timer = null;
|
|
10
|
+
context;
|
|
11
|
+
destroyed = false;
|
|
12
|
+
constructor(options) {
|
|
13
|
+
this.endpoint = options.endpoint.replace(/\/+$/, '');
|
|
14
|
+
this.sessionId = options.sessionId ?? globalThis.crypto.randomUUID();
|
|
15
|
+
this.batchSize = options.batchSize ?? 20;
|
|
16
|
+
this.flushIntervalMs = options.flushIntervalMs ?? 5000;
|
|
17
|
+
this.onError = options.onError ?? (() => { });
|
|
18
|
+
this.debug = options.debug ?? false;
|
|
19
|
+
this.context = {
|
|
20
|
+
tenantId: options.tenantId,
|
|
21
|
+
locale: options.locale,
|
|
22
|
+
};
|
|
23
|
+
this.timer = setInterval(() => this.flush(), this.flushIntervalMs);
|
|
24
|
+
if (typeof globalThis.document !== 'undefined') {
|
|
25
|
+
globalThis.document.addEventListener('visibilitychange', this.handleVisibilityChange);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
track(name, properties) {
|
|
29
|
+
if (this.destroyed)
|
|
30
|
+
return;
|
|
31
|
+
this.enqueue({
|
|
32
|
+
name,
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
sessionId: this.sessionId,
|
|
35
|
+
tenantId: this.context.tenantId,
|
|
36
|
+
locale: this.context.locale,
|
|
37
|
+
properties,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
trackUi(name, options) {
|
|
41
|
+
if (this.destroyed)
|
|
42
|
+
return;
|
|
43
|
+
this.enqueue({
|
|
44
|
+
name,
|
|
45
|
+
timestamp: Date.now(),
|
|
46
|
+
sessionId: this.sessionId,
|
|
47
|
+
tenantId: this.context.tenantId,
|
|
48
|
+
locale: this.context.locale,
|
|
49
|
+
action: options.action,
|
|
50
|
+
widgetId: options.widgetId,
|
|
51
|
+
toolName: options.toolName,
|
|
52
|
+
properties: options.properties,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
setContext(ctx) {
|
|
56
|
+
if (ctx.tenantId !== undefined)
|
|
57
|
+
this.context.tenantId = ctx.tenantId;
|
|
58
|
+
if (ctx.locale !== undefined)
|
|
59
|
+
this.context.locale = ctx.locale;
|
|
60
|
+
}
|
|
61
|
+
flush() {
|
|
62
|
+
if (this.queue.length === 0)
|
|
63
|
+
return;
|
|
64
|
+
const events = this.queue.splice(0);
|
|
65
|
+
this.send(events);
|
|
66
|
+
}
|
|
67
|
+
destroy() {
|
|
68
|
+
this.destroyed = true;
|
|
69
|
+
if (this.timer !== null) {
|
|
70
|
+
clearInterval(this.timer);
|
|
71
|
+
this.timer = null;
|
|
72
|
+
}
|
|
73
|
+
if (typeof globalThis.document !== 'undefined') {
|
|
74
|
+
globalThis.document.removeEventListener('visibilitychange', this.handleVisibilityChange);
|
|
75
|
+
}
|
|
76
|
+
this.flush();
|
|
77
|
+
}
|
|
78
|
+
enqueue(event) {
|
|
79
|
+
this.queue.push(event);
|
|
80
|
+
if (this.queue.length >= this.batchSize) {
|
|
81
|
+
this.flush();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
send(events) {
|
|
85
|
+
const url = `${this.endpoint}/events`;
|
|
86
|
+
const body = JSON.stringify({ events });
|
|
87
|
+
if (this.debug) {
|
|
88
|
+
console.debug('[telemetry-client] sending', events.length, 'events');
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
fetch(url, {
|
|
92
|
+
method: 'POST',
|
|
93
|
+
headers: { 'Content-Type': 'application/json' },
|
|
94
|
+
body,
|
|
95
|
+
keepalive: true,
|
|
96
|
+
}).catch((error) => {
|
|
97
|
+
try {
|
|
98
|
+
this.onError(error);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// never throw from onError
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
try {
|
|
107
|
+
this.onError(error);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// never throw from onError
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
handleVisibilityChange = () => {
|
|
115
|
+
if (globalThis.document?.visibilityState === 'hidden') {
|
|
116
|
+
this.flush();
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
export function createTelemetryClient(options) {
|
|
121
|
+
return new TelemetryClient(options);
|
|
122
|
+
}
|
|
123
|
+
//# sourceMappingURL=client.js.map
|
package/dist/easy-setup.d.ts
CHANGED
|
@@ -2,17 +2,21 @@ export interface EasySetupConfig {
|
|
|
2
2
|
serviceName: string;
|
|
3
3
|
serviceVersion?: string;
|
|
4
4
|
environment?: 'dev' | 'staging' | 'production';
|
|
5
|
+
/** @deprecated Use `listoApiUrl` instead. */
|
|
5
6
|
insightsApiUrl?: string;
|
|
7
|
+
/** @deprecated Use `listoApiKey` instead. */
|
|
6
8
|
insightsApiKey?: string;
|
|
9
|
+
listoApiUrl?: string;
|
|
10
|
+
listoApiKey?: string;
|
|
7
11
|
enableLocalDashboard?: boolean;
|
|
8
12
|
sampleRate?: number;
|
|
9
13
|
}
|
|
10
14
|
/**
|
|
11
15
|
* Create an observability instance with environment-based defaults.
|
|
12
16
|
*
|
|
13
|
-
* This is the easiest way to get started
|
|
17
|
+
* This is the easiest way to get started.
|
|
14
18
|
* It automatically:
|
|
15
|
-
* - Configures RemoteSink if
|
|
19
|
+
* - Configures RemoteSink if LISTO_API_KEY is provided
|
|
16
20
|
* - Configures InMemorySink + local dashboard in development
|
|
17
21
|
* - Applies sensible defaults (sampling, redaction, etc.)
|
|
18
22
|
* - Auto-detects tenant/user from common headers
|
|
@@ -26,8 +30,8 @@ export interface EasySetupConfig {
|
|
|
26
30
|
* ```
|
|
27
31
|
*
|
|
28
32
|
* Environment variables used:
|
|
29
|
-
* -
|
|
30
|
-
* -
|
|
33
|
+
* - LISTO_API_URL: Remote telemetry API endpoint
|
|
34
|
+
* - LISTO_API_KEY: API key for authentication
|
|
31
35
|
* - NODE_ENV: Determines if dev/production mode
|
|
32
36
|
*/
|
|
33
37
|
export declare function createMcpObservabilityEasy(config: EasySetupConfig): import("./index.js").McpObservability;
|
package/dist/easy-setup.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"easy-setup.d.ts","sourceRoot":"","sources":["../src/easy-setup.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;IAC/C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,eAAe,
|
|
1
|
+
{"version":3,"file":"easy-setup.d.ts","sourceRoot":"","sources":["../src/easy-setup.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,eAAe;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,YAAY,CAAC;IAC/C,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,6CAA6C;IAC7C,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,eAAe,yCAyEjE"}
|
package/dist/easy-setup.js
CHANGED
|
@@ -4,9 +4,9 @@ import { TELEMETRY_SINK_KEY, OBSERVABILITY_KEY, setGlobal, } from './endpoints.j
|
|
|
4
4
|
/**
|
|
5
5
|
* Create an observability instance with environment-based defaults.
|
|
6
6
|
*
|
|
7
|
-
* This is the easiest way to get started
|
|
7
|
+
* This is the easiest way to get started.
|
|
8
8
|
* It automatically:
|
|
9
|
-
* - Configures RemoteSink if
|
|
9
|
+
* - Configures RemoteSink if LISTO_API_KEY is provided
|
|
10
10
|
* - Configures InMemorySink + local dashboard in development
|
|
11
11
|
* - Applies sensible defaults (sampling, redaction, etc.)
|
|
12
12
|
* - Auto-detects tenant/user from common headers
|
|
@@ -20,12 +20,16 @@ import { TELEMETRY_SINK_KEY, OBSERVABILITY_KEY, setGlobal, } from './endpoints.j
|
|
|
20
20
|
* ```
|
|
21
21
|
*
|
|
22
22
|
* Environment variables used:
|
|
23
|
-
* -
|
|
24
|
-
* -
|
|
23
|
+
* - LISTO_API_URL: Remote telemetry API endpoint
|
|
24
|
+
* - LISTO_API_KEY: API key for authentication
|
|
25
25
|
* - NODE_ENV: Determines if dev/production mode
|
|
26
26
|
*/
|
|
27
27
|
export function createMcpObservabilityEasy(config) {
|
|
28
|
-
const { serviceName, serviceVersion = '1.0.0', environment = process.env.NODE_ENV === 'production' ? 'production' : 'dev',
|
|
28
|
+
const { serviceName, serviceVersion = '1.0.0', environment = process.env.NODE_ENV === 'production' ? 'production' : 'dev', listoApiUrl = config.insightsApiUrl ??
|
|
29
|
+
process.env.LISTO_API_URL ??
|
|
30
|
+
process.env.INSIGHTS_API_URL, listoApiKey = config.insightsApiKey ??
|
|
31
|
+
process.env.LISTO_API_KEY ??
|
|
32
|
+
process.env.INSIGHTS_API_KEY, enableLocalDashboard = environment === 'dev', sampleRate = environment === 'production' ? 0.1 : 1.0, } = config;
|
|
29
33
|
const sinks = [];
|
|
30
34
|
// Console logging in dev only
|
|
31
35
|
if (environment === 'dev') {
|
|
@@ -38,20 +42,19 @@ export function createMcpObservabilityEasy(config) {
|
|
|
38
42
|
// Export globally for telemetry endpoints to access
|
|
39
43
|
setGlobal(TELEMETRY_SINK_KEY, telemetrySink);
|
|
40
44
|
}
|
|
41
|
-
// Remote
|
|
42
|
-
if (
|
|
45
|
+
// Remote telemetry sink
|
|
46
|
+
if (listoApiUrl && listoApiKey) {
|
|
43
47
|
sinks.push(createRemoteSink({
|
|
44
|
-
endpoint: `${
|
|
45
|
-
apiKey:
|
|
48
|
+
endpoint: `${listoApiUrl}/v1/events/batch`,
|
|
49
|
+
apiKey: listoApiKey,
|
|
46
50
|
batchSize: 50,
|
|
47
51
|
flushIntervalMs: 5000,
|
|
48
52
|
maxRetries: 3,
|
|
49
53
|
}));
|
|
50
54
|
}
|
|
51
55
|
else if (environment !== 'dev') {
|
|
52
|
-
console.warn('[
|
|
53
|
-
'Set
|
|
54
|
-
'Generate a key at: https://app.listoai.co/settings');
|
|
56
|
+
console.warn('[telemetry] No API key configured. ' +
|
|
57
|
+
'Set LISTO_API_KEY environment variable to enable remote telemetry.');
|
|
55
58
|
}
|
|
56
59
|
// Create the observability instance
|
|
57
60
|
const options = {
|
|
@@ -60,7 +63,7 @@ export function createMcpObservabilityEasy(config) {
|
|
|
60
63
|
environment,
|
|
61
64
|
sampleRate,
|
|
62
65
|
capturePayloads: true,
|
|
63
|
-
redactKeys
|
|
66
|
+
// Omit redactKeys to use the SDK defaults (includes PII keys)
|
|
64
67
|
sinks: sinks.length > 0 ? sinks : [createConsoleSink({ logSuccess: false })],
|
|
65
68
|
// Auto-detect tenant from common headers
|
|
66
69
|
tenantResolver: (req) => req.headers['x-tenant-id'] ||
|
package/dist/endpoints.d.ts
CHANGED
|
@@ -22,13 +22,17 @@ export declare function expressTelemetry(observability: McpObservability): (req:
|
|
|
22
22
|
* - GET /telemetry/dashboard - HTML dashboard
|
|
23
23
|
* - POST /telemetry/event - UI event ingestion
|
|
24
24
|
*
|
|
25
|
+
* @param expressModule - The express module instance. Required because the SDK
|
|
26
|
+
* cannot resolve express from the consumer's node_modules automatically.
|
|
27
|
+
*
|
|
25
28
|
* @example
|
|
26
29
|
* ```typescript
|
|
30
|
+
* import express from 'express';
|
|
27
31
|
* import { createTelemetryRouter } from '@listo-ai/mcp-observability';
|
|
28
32
|
*
|
|
29
|
-
* app.use('/telemetry', createTelemetryRouter());
|
|
33
|
+
* app.use('/telemetry', createTelemetryRouter(express));
|
|
30
34
|
* ```
|
|
31
35
|
*/
|
|
32
|
-
export declare function createTelemetryRouter(): any;
|
|
36
|
+
export declare function createTelemetryRouter(expressModule: any): any;
|
|
33
37
|
export { TELEMETRY_SINK_KEY, OBSERVABILITY_KEY, setGlobal };
|
|
34
38
|
//# sourceMappingURL=endpoints.d.ts.map
|
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,
|
|
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;AA4BD;;;;;;;;;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
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const TELEMETRY_SINK_KEY = Symbol.for('
|
|
2
|
-
const OBSERVABILITY_KEY = Symbol.for('
|
|
1
|
+
const TELEMETRY_SINK_KEY = Symbol.for('mcp-obs.telemetrySink');
|
|
2
|
+
const OBSERVABILITY_KEY = Symbol.for('mcp-obs.observability');
|
|
3
3
|
function escapeHtml(str) {
|
|
4
4
|
return str
|
|
5
5
|
.replace(/&/g, '&')
|
|
@@ -71,17 +71,20 @@ export function expressTelemetry(observability) {
|
|
|
71
71
|
* - GET /telemetry/dashboard - HTML dashboard
|
|
72
72
|
* - POST /telemetry/event - UI event ingestion
|
|
73
73
|
*
|
|
74
|
+
* @param expressModule - The express module instance. Required because the SDK
|
|
75
|
+
* cannot resolve express from the consumer's node_modules automatically.
|
|
76
|
+
*
|
|
74
77
|
* @example
|
|
75
78
|
* ```typescript
|
|
79
|
+
* import express from 'express';
|
|
76
80
|
* import { createTelemetryRouter } from '@listo-ai/mcp-observability';
|
|
77
81
|
*
|
|
78
|
-
* app.use('/telemetry', createTelemetryRouter());
|
|
82
|
+
* app.use('/telemetry', createTelemetryRouter(express));
|
|
79
83
|
* ```
|
|
80
84
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
const
|
|
84
|
-
const router = express.Router();
|
|
85
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
86
|
+
export function createTelemetryRouter(expressModule) {
|
|
87
|
+
const router = expressModule.Router();
|
|
85
88
|
const getTelemetrySink = () => {
|
|
86
89
|
return getGlobal(TELEMETRY_SINK_KEY);
|
|
87
90
|
};
|
|
@@ -110,14 +113,14 @@ export function createTelemetryRouter() {
|
|
|
110
113
|
<!DOCTYPE html>
|
|
111
114
|
<html>
|
|
112
115
|
<head>
|
|
113
|
-
<title>
|
|
116
|
+
<title>Telemetry Dashboard</title>
|
|
114
117
|
<style>
|
|
115
118
|
body { font-family: system-ui, sans-serif; padding: 20px; }
|
|
116
119
|
.error { color: #d32f2f; }
|
|
117
120
|
</style>
|
|
118
121
|
</head>
|
|
119
122
|
<body>
|
|
120
|
-
<h1>
|
|
123
|
+
<h1>Telemetry Dashboard</h1>
|
|
121
124
|
<p class="error">Local telemetry is not enabled.</p>
|
|
122
125
|
<p>To enable, set enableLocalDashboard: true in createMcpObservabilityEasy config.</p>
|
|
123
126
|
</body>
|
|
@@ -131,7 +134,7 @@ export function createTelemetryRouter() {
|
|
|
131
134
|
<head>
|
|
132
135
|
<meta charset="UTF-8">
|
|
133
136
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
134
|
-
<title>
|
|
137
|
+
<title>Telemetry Dashboard</title>
|
|
135
138
|
<style>
|
|
136
139
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
137
140
|
body {
|
|
@@ -186,7 +189,7 @@ export function createTelemetryRouter() {
|
|
|
186
189
|
<small style="margin-left: 10px; color: #666;">Auto-refreshes every 5 seconds</small>
|
|
187
190
|
</div>
|
|
188
191
|
|
|
189
|
-
<h1
|
|
192
|
+
<h1>Telemetry Dashboard</h1>
|
|
190
193
|
|
|
191
194
|
<div class="grid">
|
|
192
195
|
<div class="card">
|
|
@@ -270,8 +273,7 @@ export function createTelemetryRouter() {
|
|
|
270
273
|
: ''}
|
|
271
274
|
|
|
272
275
|
<p style="margin-top: 40px; color: #999; font-size: 12px;">
|
|
273
|
-
Last updated: ${new Date().toLocaleTimeString()}
|
|
274
|
-
View in <a href="https://app.listoai.co" target="_blank" style="color: #1976d2;">Listo</a>
|
|
276
|
+
Last updated: ${new Date().toLocaleTimeString()}
|
|
275
277
|
</p>
|
|
276
278
|
</div>
|
|
277
279
|
|
|
@@ -287,7 +289,7 @@ export function createTelemetryRouter() {
|
|
|
287
289
|
.send(html);
|
|
288
290
|
});
|
|
289
291
|
// UI event ingestion endpoint
|
|
290
|
-
router.post('/event',
|
|
292
|
+
router.post('/event', expressModule.json({ limit: MAX_EVENT_BODY_SIZE }), (req, res) => {
|
|
291
293
|
const validated = validateUiEventBody(req.body);
|
|
292
294
|
if (!validated) {
|
|
293
295
|
res
|
|
@@ -301,6 +303,35 @@ export function createTelemetryRouter() {
|
|
|
301
303
|
}
|
|
302
304
|
res.status(201).json({ ok: true });
|
|
303
305
|
});
|
|
306
|
+
// Batch UI event ingestion endpoint
|
|
307
|
+
router.post('/events', expressModule.json({ limit: MAX_EVENT_BODY_SIZE * 100 }), (req, res) => {
|
|
308
|
+
const body = req.body;
|
|
309
|
+
if (!body || !Array.isArray(body.events)) {
|
|
310
|
+
res.status(400).json({ error: 'Expected { events: [...] } array' });
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
const events = body.events;
|
|
314
|
+
if (events.length > 100) {
|
|
315
|
+
res.status(400).json({ error: 'Maximum 100 events per batch' });
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
const observability = getGlobal(OBSERVABILITY_KEY);
|
|
319
|
+
let accepted = 0;
|
|
320
|
+
let rejected = 0;
|
|
321
|
+
for (const raw of events) {
|
|
322
|
+
const validated = validateUiEventBody(raw);
|
|
323
|
+
if (validated) {
|
|
324
|
+
if (observability) {
|
|
325
|
+
observability.recordUiEvent(validated);
|
|
326
|
+
}
|
|
327
|
+
accepted++;
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
rejected++;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
res.status(201).json({ ok: true, accepted, rejected });
|
|
334
|
+
});
|
|
304
335
|
return router;
|
|
305
336
|
}
|
|
306
337
|
export { TELEMETRY_SINK_KEY, OBSERVABILITY_KEY, setGlobal };
|
package/dist/index.d.ts
CHANGED
|
@@ -209,66 +209,6 @@ export declare class InMemorySink implements EventSink {
|
|
|
209
209
|
error: number;
|
|
210
210
|
}>;
|
|
211
211
|
};
|
|
212
|
-
analytics: {
|
|
213
|
-
search: {
|
|
214
|
-
topQueries: CountEntry[];
|
|
215
|
-
topKeywords: CountEntry[];
|
|
216
|
-
minRatings: CountEntry[];
|
|
217
|
-
priceLevels: CountEntry[];
|
|
218
|
-
neighborhoods: CountEntry[];
|
|
219
|
-
styles: CountEntry[];
|
|
220
|
-
limits: CountEntry[];
|
|
221
|
-
};
|
|
222
|
-
geo: {
|
|
223
|
-
topCities: CountEntry[];
|
|
224
|
-
topCountries: CountEntry[];
|
|
225
|
-
};
|
|
226
|
-
hotels: {
|
|
227
|
-
topViewed: {
|
|
228
|
-
id: string;
|
|
229
|
-
name?: string;
|
|
230
|
-
count: number;
|
|
231
|
-
}[];
|
|
232
|
-
topBookingClicks: {
|
|
233
|
-
id: string;
|
|
234
|
-
name?: string;
|
|
235
|
-
count: number;
|
|
236
|
-
}[];
|
|
237
|
-
topBookingCtr: {
|
|
238
|
-
id: string;
|
|
239
|
-
name: string | undefined;
|
|
240
|
-
clicks: number;
|
|
241
|
-
views: number;
|
|
242
|
-
ctr: number | null;
|
|
243
|
-
}[];
|
|
244
|
-
topWebsiteClicks: {
|
|
245
|
-
id: string;
|
|
246
|
-
name?: string;
|
|
247
|
-
count: number;
|
|
248
|
-
}[];
|
|
249
|
-
viewedPriceLevels: CountEntry[];
|
|
250
|
-
viewedStyles: CountEntry[];
|
|
251
|
-
viewedCategories: CountEntry[];
|
|
252
|
-
viewedNeighborhoods: CountEntry[];
|
|
253
|
-
};
|
|
254
|
-
ctr: {
|
|
255
|
-
impressions: number;
|
|
256
|
-
clicks: number;
|
|
257
|
-
detailViews: number;
|
|
258
|
-
bookingClicks: number;
|
|
259
|
-
websiteClicks: number;
|
|
260
|
-
bookingPerImpression: number | null;
|
|
261
|
-
bookingPerDetailView: number | null;
|
|
262
|
-
};
|
|
263
|
-
sessions: {
|
|
264
|
-
total: number;
|
|
265
|
-
converted: number;
|
|
266
|
-
booking: number;
|
|
267
|
-
website: number;
|
|
268
|
-
conversionRate: number | null;
|
|
269
|
-
unknownSessionUiEvents: number;
|
|
270
|
-
};
|
|
271
|
-
};
|
|
272
212
|
};
|
|
273
213
|
}
|
|
274
214
|
export declare function createConsoleSink(options?: {
|
|
@@ -295,10 +235,6 @@ export declare class McpObservability {
|
|
|
295
235
|
private safeEmit;
|
|
296
236
|
}
|
|
297
237
|
export declare function createMcpObservability(options: ObservabilityOptions): McpObservability;
|
|
298
|
-
type CountEntry = {
|
|
299
|
-
value: string;
|
|
300
|
-
count: number;
|
|
301
|
-
};
|
|
302
238
|
export { RemoteSink, createRemoteSink, type RemoteSinkOptions, } from './remote-sink.js';
|
|
303
239
|
export { createMcpObservabilityEasy, type EasySetupConfig, } from './easy-setup.js';
|
|
304
240
|
export { createTelemetryRouter, expressTelemetry } from './endpoints.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -40,7 +40,6 @@ export class InMemorySink {
|
|
|
40
40
|
sessions: summarizeSessionEvents(sessionEvents),
|
|
41
41
|
ui: summarizeUiEvents(uiEvents),
|
|
42
42
|
business: summarizeBusinessEvents(businessEvents),
|
|
43
|
-
analytics: computeAnalytics({ uiEvents, businessEvents }),
|
|
44
43
|
};
|
|
45
44
|
}
|
|
46
45
|
}
|
|
@@ -112,6 +111,12 @@ export class McpObservability {
|
|
|
112
111
|
'password',
|
|
113
112
|
'token',
|
|
114
113
|
'secret',
|
|
114
|
+
'userid',
|
|
115
|
+
'userlocation',
|
|
116
|
+
'email',
|
|
117
|
+
'useremail',
|
|
118
|
+
'phone',
|
|
119
|
+
'phonenumber',
|
|
115
120
|
]).map((key) => key.toLowerCase()));
|
|
116
121
|
this.capturePayloads = options.capturePayloads ?? false;
|
|
117
122
|
this.tenantResolver = options.tenantResolver;
|
|
@@ -124,6 +129,9 @@ export class McpObservability {
|
|
|
124
129
|
service: this.serviceName,
|
|
125
130
|
serviceVersion: this.serviceVersion,
|
|
126
131
|
environment: this.environment,
|
|
132
|
+
properties: event.properties
|
|
133
|
+
? this.sanitizePayload(event.properties)
|
|
134
|
+
: undefined,
|
|
127
135
|
};
|
|
128
136
|
this.safeEmit(enriched);
|
|
129
137
|
}
|
|
@@ -273,7 +281,9 @@ export class McpObservability {
|
|
|
273
281
|
serviceVersion: this.serviceVersion,
|
|
274
282
|
environment: this.environment,
|
|
275
283
|
name,
|
|
276
|
-
properties
|
|
284
|
+
properties: properties
|
|
285
|
+
? this.sanitizePayload(properties)
|
|
286
|
+
: undefined,
|
|
277
287
|
status,
|
|
278
288
|
};
|
|
279
289
|
this.safeEmit(event);
|
|
@@ -469,321 +479,6 @@ function truncatePreview(value) {
|
|
|
469
479
|
return truncateString(value);
|
|
470
480
|
return value;
|
|
471
481
|
}
|
|
472
|
-
function incMap(map, rawKey, delta = 1) {
|
|
473
|
-
const key = rawKey.trim();
|
|
474
|
-
if (!key)
|
|
475
|
-
return;
|
|
476
|
-
map.set(key, (map.get(key) ?? 0) + delta);
|
|
477
|
-
}
|
|
478
|
-
function asString(value) {
|
|
479
|
-
if (typeof value === 'string')
|
|
480
|
-
return value;
|
|
481
|
-
if (typeof value === 'number' || typeof value === 'boolean')
|
|
482
|
-
return String(value);
|
|
483
|
-
return undefined;
|
|
484
|
-
}
|
|
485
|
-
function asObject(value) {
|
|
486
|
-
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
487
|
-
return undefined;
|
|
488
|
-
return value;
|
|
489
|
-
}
|
|
490
|
-
function asStringArray(value) {
|
|
491
|
-
if (!Array.isArray(value))
|
|
492
|
-
return [];
|
|
493
|
-
return value
|
|
494
|
-
.map((v) => asString(v))
|
|
495
|
-
.filter((v) => typeof v === 'string' && v.trim().length > 0);
|
|
496
|
-
}
|
|
497
|
-
function tokenize(input, stopWords) {
|
|
498
|
-
const sanitized = input
|
|
499
|
-
.toLowerCase()
|
|
500
|
-
.replace(/[^a-z0-9]+/g, ' ')
|
|
501
|
-
.trim();
|
|
502
|
-
if (!sanitized)
|
|
503
|
-
return [];
|
|
504
|
-
return sanitized
|
|
505
|
-
.split(/\s+/)
|
|
506
|
-
.map((token) => token.trim())
|
|
507
|
-
.filter((token) => token.length >= 3)
|
|
508
|
-
.filter((token) => !stopWords.has(token));
|
|
509
|
-
}
|
|
510
|
-
function topEntries(map, limit = 10) {
|
|
511
|
-
return Array.from(map.entries())
|
|
512
|
-
.sort((a, b) => b[1] - a[1])
|
|
513
|
-
.slice(0, limit)
|
|
514
|
-
.map(([value, count]) => ({ value, count }));
|
|
515
|
-
}
|
|
516
|
-
function topHotelEntries(map, limit = 10) {
|
|
517
|
-
return Array.from(map.entries())
|
|
518
|
-
.sort((a, b) => b[1].count - a[1].count)
|
|
519
|
-
.slice(0, limit)
|
|
520
|
-
.map(([id, meta]) => ({ id, name: meta.name, count: meta.count }));
|
|
521
|
-
}
|
|
522
|
-
function computeAnalytics(options) {
|
|
523
|
-
const queryCounts = new Map();
|
|
524
|
-
const keywordCounts = new Map();
|
|
525
|
-
const minRatingCounts = new Map();
|
|
526
|
-
const cityCounts = new Map();
|
|
527
|
-
const countryCounts = new Map();
|
|
528
|
-
const hotelViewCounts = new Map();
|
|
529
|
-
const bookingClickCounts = new Map();
|
|
530
|
-
const websiteClickCounts = new Map();
|
|
531
|
-
const searchPriceCounts = new Map();
|
|
532
|
-
const searchStyleCounts = new Map();
|
|
533
|
-
const searchNeighborhoodCounts = new Map();
|
|
534
|
-
const searchLimitCounts = new Map();
|
|
535
|
-
const viewedPriceCounts = new Map();
|
|
536
|
-
const viewedStyleCounts = new Map();
|
|
537
|
-
const viewedCategoryCounts = new Map();
|
|
538
|
-
const viewedNeighborhoodCounts = new Map();
|
|
539
|
-
const stopWords = new Set([
|
|
540
|
-
'a',
|
|
541
|
-
'an',
|
|
542
|
-
'and',
|
|
543
|
-
'are',
|
|
544
|
-
'as',
|
|
545
|
-
'at',
|
|
546
|
-
'be',
|
|
547
|
-
'but',
|
|
548
|
-
'by',
|
|
549
|
-
'for',
|
|
550
|
-
'from',
|
|
551
|
-
'has',
|
|
552
|
-
'have',
|
|
553
|
-
'hotel',
|
|
554
|
-
'hotels',
|
|
555
|
-
'i',
|
|
556
|
-
'in',
|
|
557
|
-
'is',
|
|
558
|
-
'it',
|
|
559
|
-
'of',
|
|
560
|
-
'on',
|
|
561
|
-
'or',
|
|
562
|
-
'the',
|
|
563
|
-
'to',
|
|
564
|
-
'with',
|
|
565
|
-
]);
|
|
566
|
-
const recordQuery = (query) => {
|
|
567
|
-
const normalized = query.trim();
|
|
568
|
-
if (!normalized)
|
|
569
|
-
return;
|
|
570
|
-
incMap(queryCounts, normalized);
|
|
571
|
-
tokenize(normalized, stopWords).forEach((token) => incMap(keywordCounts, token));
|
|
572
|
-
};
|
|
573
|
-
const recordMinRating = (value) => {
|
|
574
|
-
const str = asString(value);
|
|
575
|
-
if (!str)
|
|
576
|
-
return;
|
|
577
|
-
incMap(minRatingCounts, str);
|
|
578
|
-
};
|
|
579
|
-
const recordCityCountry = (city, country) => {
|
|
580
|
-
if (city)
|
|
581
|
-
incMap(cityCounts, city);
|
|
582
|
-
if (country)
|
|
583
|
-
incMap(countryCounts, country);
|
|
584
|
-
};
|
|
585
|
-
const recordHotelView = (hotelId, name) => {
|
|
586
|
-
if (!hotelId)
|
|
587
|
-
return;
|
|
588
|
-
const existing = hotelViewCounts.get(hotelId) ?? {
|
|
589
|
-
count: 0,
|
|
590
|
-
name: undefined,
|
|
591
|
-
};
|
|
592
|
-
hotelViewCounts.set(hotelId, {
|
|
593
|
-
count: existing.count + 1,
|
|
594
|
-
name: existing.name ?? (name || undefined),
|
|
595
|
-
});
|
|
596
|
-
};
|
|
597
|
-
const recordHotelClick = (map, hotelId, name) => {
|
|
598
|
-
if (!hotelId)
|
|
599
|
-
return;
|
|
600
|
-
const existing = map.get(hotelId) ?? { count: 0, name: undefined };
|
|
601
|
-
map.set(hotelId, {
|
|
602
|
-
count: existing.count + 1,
|
|
603
|
-
name: existing.name ?? (name || undefined),
|
|
604
|
-
});
|
|
605
|
-
};
|
|
606
|
-
const recordList = (map, values) => {
|
|
607
|
-
const list = asStringArray(values);
|
|
608
|
-
list.forEach((value) => incMap(map, value));
|
|
609
|
-
};
|
|
610
|
-
// Business events (tool-level signals)
|
|
611
|
-
for (const event of options.businessEvents) {
|
|
612
|
-
if (event.name === 'hotel_search') {
|
|
613
|
-
const q = asString(event.properties?.q);
|
|
614
|
-
if (q)
|
|
615
|
-
recordQuery(q);
|
|
616
|
-
recordMinRating(event.properties?.minRating ?? event.properties?.min_rating);
|
|
617
|
-
recordCityCountry(asString(event.properties?.city), asString(event.properties?.country));
|
|
618
|
-
recordList(searchPriceCounts, event.properties?.priceLevels);
|
|
619
|
-
recordList(searchStyleCounts, event.properties?.styles);
|
|
620
|
-
recordList(searchNeighborhoodCounts, event.properties?.neighborhoods);
|
|
621
|
-
const limit = asString(event.properties?.limit);
|
|
622
|
-
if (limit)
|
|
623
|
-
incMap(searchLimitCounts, limit);
|
|
624
|
-
}
|
|
625
|
-
if (event.name === 'hotel_details') {
|
|
626
|
-
const hotelId = asString(event.properties?.id);
|
|
627
|
-
const name = asString(event.properties?.name);
|
|
628
|
-
recordHotelView(hotelId, name);
|
|
629
|
-
recordCityCountry(asString(event.properties?.city), asString(event.properties?.country));
|
|
630
|
-
const priceLevel = asString(event.properties?.priceLevel);
|
|
631
|
-
if (priceLevel)
|
|
632
|
-
incMap(viewedPriceCounts, priceLevel);
|
|
633
|
-
recordList(viewedStyleCounts, event.properties?.styles);
|
|
634
|
-
recordList(viewedCategoryCounts, event.properties?.categories);
|
|
635
|
-
recordList(viewedNeighborhoodCounts, event.properties?.neighborhoods);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
// UI events (engagement/CTR)
|
|
639
|
-
for (const event of options.uiEvents) {
|
|
640
|
-
if (event.name === 'search_results_rendered') {
|
|
641
|
-
const filters = asObject(event.properties?.rawFilters) ??
|
|
642
|
-
asObject(event.properties?.filters);
|
|
643
|
-
const q = asString(filters?.q ?? filters?.query);
|
|
644
|
-
if (q)
|
|
645
|
-
recordQuery(q);
|
|
646
|
-
recordMinRating(filters?.minRating ?? filters?.min_rating);
|
|
647
|
-
recordList(searchPriceCounts, filters?.priceLevels ?? filters?.price_levels);
|
|
648
|
-
recordList(searchStyleCounts, filters?.styles);
|
|
649
|
-
recordList(searchNeighborhoodCounts, filters?.neighborhoods);
|
|
650
|
-
const limit = asString(filters?.limit);
|
|
651
|
-
if (limit)
|
|
652
|
-
incMap(searchLimitCounts, limit);
|
|
653
|
-
}
|
|
654
|
-
if (event.name === 'hotel_detail_viewed') {
|
|
655
|
-
const props = asObject(event.properties);
|
|
656
|
-
recordHotelView(asString(props?.hotelId), asString(props?.hotelName));
|
|
657
|
-
recordCityCountry(asString(props?.city), asString(props?.country));
|
|
658
|
-
const priceLevel = asString(props?.priceLevel);
|
|
659
|
-
if (priceLevel)
|
|
660
|
-
incMap(viewedPriceCounts, priceLevel);
|
|
661
|
-
recordList(viewedStyleCounts, props?.styles);
|
|
662
|
-
recordList(viewedCategoryCounts, props?.categories);
|
|
663
|
-
recordList(viewedNeighborhoodCounts, props?.neighborhoods);
|
|
664
|
-
}
|
|
665
|
-
if (event.name === 'hotel_selected') {
|
|
666
|
-
const props = asObject(event.properties);
|
|
667
|
-
recordHotelView(asString(props?.hotelId), asString(props?.hotelName));
|
|
668
|
-
}
|
|
669
|
-
if (event.name === 'booking_cta_click') {
|
|
670
|
-
const props = asObject(event.properties);
|
|
671
|
-
const hotelId = asString(props?.hotelId);
|
|
672
|
-
recordHotelClick(bookingClickCounts, hotelId, asString(props?.hotelName));
|
|
673
|
-
}
|
|
674
|
-
if (event.name === 'website_click') {
|
|
675
|
-
const props = asObject(event.properties);
|
|
676
|
-
const hotelId = asString(props?.hotelId);
|
|
677
|
-
recordHotelClick(websiteClickCounts, hotelId, asString(props?.hotelName));
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
const impressions = options.uiEvents.filter((e) => e.name === 'widget_impression').length;
|
|
681
|
-
const clicks = options.uiEvents.filter((e) => e.name === 'widget_click').length;
|
|
682
|
-
const detailViews = options.uiEvents.filter((e) => e.name === 'hotel_detail_viewed').length;
|
|
683
|
-
const bookingClicks = Array.from(bookingClickCounts.values()).reduce((acc, entry) => acc + (entry.count || 0), 0);
|
|
684
|
-
const websiteClicks = Array.from(websiteClickCounts.values()).reduce((acc, entry) => acc + (entry.count || 0), 0);
|
|
685
|
-
// Backfill missing names from any known hotel views (older clients may omit hotelName).
|
|
686
|
-
for (const [hotelId, meta] of bookingClickCounts.entries()) {
|
|
687
|
-
if (!meta.name) {
|
|
688
|
-
const known = hotelViewCounts.get(hotelId)?.name;
|
|
689
|
-
if (known)
|
|
690
|
-
bookingClickCounts.set(hotelId, { ...meta, name: known });
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
for (const [hotelId, meta] of websiteClickCounts.entries()) {
|
|
694
|
-
if (!meta.name) {
|
|
695
|
-
const known = hotelViewCounts.get(hotelId)?.name;
|
|
696
|
-
if (known)
|
|
697
|
-
websiteClickCounts.set(hotelId, { ...meta, name: known });
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
const ctrFromImpressions = impressions ? bookingClicks / impressions : null;
|
|
701
|
-
const ctrFromDetails = detailViews ? bookingClicks / detailViews : null;
|
|
702
|
-
const topBookingCtr = Array.from(bookingClickCounts.entries())
|
|
703
|
-
.map(([hotelId, meta]) => {
|
|
704
|
-
const clicks = meta.count || 0;
|
|
705
|
-
const views = hotelViewCounts.get(hotelId)?.count ?? 0;
|
|
706
|
-
const name = meta.name ?? hotelViewCounts.get(hotelId)?.name;
|
|
707
|
-
return {
|
|
708
|
-
id: hotelId,
|
|
709
|
-
name,
|
|
710
|
-
clicks,
|
|
711
|
-
views,
|
|
712
|
-
ctr: views ? clicks / views : null,
|
|
713
|
-
};
|
|
714
|
-
})
|
|
715
|
-
.filter((row) => row.ctr !== null && row.views >= 2)
|
|
716
|
-
.sort((a, b) => (b.ctr ?? 0) - (a.ctr ?? 0))
|
|
717
|
-
.slice(0, 8);
|
|
718
|
-
const sessionsAll = new Set();
|
|
719
|
-
const sessionsFromImpressions = new Set();
|
|
720
|
-
const sessionsBooking = new Set();
|
|
721
|
-
const sessionsWebsite = new Set();
|
|
722
|
-
let unknownSessionUiEvents = 0;
|
|
723
|
-
for (const event of options.uiEvents) {
|
|
724
|
-
const sid = event.sessionId;
|
|
725
|
-
if (!sid) {
|
|
726
|
-
unknownSessionUiEvents += 1;
|
|
727
|
-
continue;
|
|
728
|
-
}
|
|
729
|
-
sessionsAll.add(sid);
|
|
730
|
-
if (event.name === 'widget_impression')
|
|
731
|
-
sessionsFromImpressions.add(sid);
|
|
732
|
-
if (event.name === 'booking_cta_click')
|
|
733
|
-
sessionsBooking.add(sid);
|
|
734
|
-
if (event.name === 'website_click')
|
|
735
|
-
sessionsWebsite.add(sid);
|
|
736
|
-
}
|
|
737
|
-
const totalSessions = sessionsFromImpressions.size > 0
|
|
738
|
-
? sessionsFromImpressions.size
|
|
739
|
-
: sessionsAll.size;
|
|
740
|
-
const convertedSessions = new Set([
|
|
741
|
-
...Array.from(sessionsBooking),
|
|
742
|
-
...Array.from(sessionsWebsite),
|
|
743
|
-
]).size;
|
|
744
|
-
return {
|
|
745
|
-
search: {
|
|
746
|
-
topQueries: topEntries(queryCounts, 12),
|
|
747
|
-
topKeywords: topEntries(keywordCounts, 18),
|
|
748
|
-
minRatings: topEntries(minRatingCounts, 8),
|
|
749
|
-
priceLevels: topEntries(searchPriceCounts, 10),
|
|
750
|
-
neighborhoods: topEntries(searchNeighborhoodCounts, 10),
|
|
751
|
-
styles: topEntries(searchStyleCounts, 10),
|
|
752
|
-
limits: topEntries(searchLimitCounts, 8),
|
|
753
|
-
},
|
|
754
|
-
geo: {
|
|
755
|
-
topCities: topEntries(cityCounts, 12),
|
|
756
|
-
topCountries: topEntries(countryCounts, 12),
|
|
757
|
-
},
|
|
758
|
-
hotels: {
|
|
759
|
-
topViewed: topHotelEntries(hotelViewCounts, 12),
|
|
760
|
-
topBookingClicks: topHotelEntries(bookingClickCounts, 12),
|
|
761
|
-
topBookingCtr,
|
|
762
|
-
topWebsiteClicks: topHotelEntries(websiteClickCounts, 12),
|
|
763
|
-
viewedPriceLevels: topEntries(viewedPriceCounts, 10),
|
|
764
|
-
viewedStyles: topEntries(viewedStyleCounts, 10),
|
|
765
|
-
viewedCategories: topEntries(viewedCategoryCounts, 10),
|
|
766
|
-
viewedNeighborhoods: topEntries(viewedNeighborhoodCounts, 10),
|
|
767
|
-
},
|
|
768
|
-
ctr: {
|
|
769
|
-
impressions,
|
|
770
|
-
clicks,
|
|
771
|
-
detailViews,
|
|
772
|
-
bookingClicks,
|
|
773
|
-
websiteClicks,
|
|
774
|
-
bookingPerImpression: ctrFromImpressions,
|
|
775
|
-
bookingPerDetailView: ctrFromDetails,
|
|
776
|
-
},
|
|
777
|
-
sessions: {
|
|
778
|
-
total: totalSessions,
|
|
779
|
-
converted: convertedSessions,
|
|
780
|
-
booking: sessionsBooking.size,
|
|
781
|
-
website: sessionsWebsite.size,
|
|
782
|
-
conversionRate: totalSessions ? convertedSessions / totalSessions : null,
|
|
783
|
-
unknownSessionUiEvents,
|
|
784
|
-
},
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
482
|
export { RemoteSink, createRemoteSink, } from './remote-sink.js';
|
|
788
483
|
export { createMcpObservabilityEasy, } from './easy-setup.js';
|
|
789
484
|
export { createTelemetryRouter, expressTelemetry } from './endpoints.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@listo-ai/mcp-observability",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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",
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
".": {
|
|
10
10
|
"types": "./dist/index.d.ts",
|
|
11
11
|
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./client": {
|
|
14
|
+
"types": "./dist/client.d.ts",
|
|
15
|
+
"import": "./dist/client.js"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
18
|
"files": [
|