@listo-ai/mcp-observability 0.2.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/README.md +609 -0
- package/dist/easy-setup.d.ts +34 -0
- package/dist/easy-setup.d.ts.map +1 -0
- package/dist/easy-setup.js +75 -0
- package/dist/endpoints.d.ts +34 -0
- package/dist/endpoints.d.ts.map +1 -0
- package/dist/endpoints.js +307 -0
- package/dist/index.d.ts +305 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +790 -0
- package/dist/remote-sink.d.ts +26 -0
- package/dist/remote-sink.d.ts.map +1 -0
- package/dist/remote-sink.js +123 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,609 @@
|
|
|
1
|
+
# @listo-ai/mcp-observability
|
|
2
|
+
|
|
3
|
+
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 and automatic data redaction.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This SDK provides comprehensive observability for [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) servers and Express.js applications. It integrates with Listo Insights for centralized analytics, and includes a local development dashboard for real-time metrics.
|
|
8
|
+
|
|
9
|
+
**Key features:**
|
|
10
|
+
|
|
11
|
+
- **Easy setup** -- Get started in 4 lines of code with `createMcpObservabilityEasy()`
|
|
12
|
+
- **Local dashboard** -- Real-time metrics dashboard during development at `/telemetry/dashboard`
|
|
13
|
+
- **Centralized analytics** -- Send telemetry data to Listo Insights in production
|
|
14
|
+
- **Automatic tracking** -- HTTP requests via Express middleware, MCP tool invocations via handler wrapper
|
|
15
|
+
- **Payload sanitization** -- Built-in redaction of sensitive keys (`password`, `token`, `apiKey`, `secret`, `authorization`)
|
|
16
|
+
- **Sampling** -- Configurable sampling rates with guaranteed capture of errors and session events
|
|
17
|
+
- **Zero runtime dependencies** -- Only relies on Node.js built-ins (`crypto`, `http`)
|
|
18
|
+
|
|
19
|
+
## Table of Contents
|
|
20
|
+
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Quick Start](#quick-start)
|
|
23
|
+
- [How It Works](#how-it-works)
|
|
24
|
+
- [API Reference](#api-reference)
|
|
25
|
+
- [Integration Guide](#integration-guide)
|
|
26
|
+
- [Event Types](#event-types)
|
|
27
|
+
- [Environment Variables](#environment-variables)
|
|
28
|
+
- [Security](#security)
|
|
29
|
+
- [Documentation](#documentation)
|
|
30
|
+
- [Development](#development)
|
|
31
|
+
- [CI/CD and Publishing](#cicd-and-publishing)
|
|
32
|
+
- [Troubleshooting](#troubleshooting)
|
|
33
|
+
- [License](#license)
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npm install @listo-ai/mcp-observability
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick Start
|
|
42
|
+
|
|
43
|
+
### 1. Initialize observability
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { createMcpObservabilityEasy } from '@listo-ai/mcp-observability';
|
|
47
|
+
|
|
48
|
+
const observability = createMcpObservabilityEasy({
|
|
49
|
+
serviceName: 'my-mcp-server',
|
|
50
|
+
serviceVersion: '1.0.0',
|
|
51
|
+
});
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 2. Add telemetry routes to your Express app
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { createTelemetryRouter } from '@listo-ai/mcp-observability';
|
|
58
|
+
|
|
59
|
+
app.use('/telemetry', createTelemetryRouter());
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### 3. Set environment variables
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
INSIGHTS_API_URL=https://api.listoai.co
|
|
66
|
+
INSIGHTS_API_KEY=fuse_xxxxxxxxxxxxx
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 4. Access the local dashboard
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
http://localhost:3000/telemetry/dashboard
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## How It Works
|
|
76
|
+
|
|
77
|
+
### Environment-Based Auto-Configuration
|
|
78
|
+
|
|
79
|
+
`createMcpObservabilityEasy()` automatically configures observability based on `NODE_ENV`:
|
|
80
|
+
|
|
81
|
+
| Setting | Development | Production |
|
|
82
|
+
| --- | --- | --- |
|
|
83
|
+
| Console logging | Errors only | Disabled |
|
|
84
|
+
| In-memory sink | Enabled (2000 event buffer) | Disabled |
|
|
85
|
+
| Local dashboard | Enabled | Disabled |
|
|
86
|
+
| Remote sink | Only if API key set | Enabled (if API key set) |
|
|
87
|
+
| Sample rate | 100% | 10% |
|
|
88
|
+
| Event batching | N/A | Every 5s, 50 events/batch |
|
|
89
|
+
|
|
90
|
+
### Data Flow
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
Your MCP Server / Express App
|
|
94
|
+
|
|
|
95
|
+
v
|
|
96
|
+
Observability SDK (captures events)
|
|
97
|
+
|
|
|
98
|
+
+---> Console Sink (dev only, errors)
|
|
99
|
+
+---> InMemory Sink (local dashboard)
|
|
100
|
+
+---> Remote Sink (Listo Insights, batched)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Sink Architecture
|
|
104
|
+
|
|
105
|
+
Events flow through configurable **sinks** -- decoupled consumers that process telemetry data:
|
|
106
|
+
|
|
107
|
+
- **`InMemorySink`** -- Circular buffer (default 2000 events) powering the local dashboard
|
|
108
|
+
- **`RemoteSink`** -- Batches events and sends them to Listo Insights with exponential backoff retries
|
|
109
|
+
- **`ConsoleSink`** -- Logs errors to stdout
|
|
110
|
+
- **`combineSinks()`** -- Routes events to multiple sinks simultaneously
|
|
111
|
+
|
|
112
|
+
## API Reference
|
|
113
|
+
|
|
114
|
+
### `createMcpObservabilityEasy(config)`
|
|
115
|
+
|
|
116
|
+
Creates an observability instance with sensible defaults. This is the recommended entry point.
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
interface EasySetupConfig {
|
|
120
|
+
serviceName: string;
|
|
121
|
+
serviceVersion?: string; // default: "1.0.0"
|
|
122
|
+
environment?: "dev" | "staging" | "production";
|
|
123
|
+
insightsApiUrl?: string; // default: process.env.INSIGHTS_API_URL
|
|
124
|
+
insightsApiKey?: string; // default: process.env.INSIGHTS_API_KEY
|
|
125
|
+
enableLocalDashboard?: boolean; // default: true in dev, false in prod
|
|
126
|
+
sampleRate?: number; // default: 1 in dev, 0.1 in prod
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
const observability = createMcpObservabilityEasy({
|
|
132
|
+
serviceName: 'hotel-search-mcp',
|
|
133
|
+
serviceVersion: '0.1.0',
|
|
134
|
+
enableLocalDashboard: true,
|
|
135
|
+
});
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `createMcpObservability(options)`
|
|
139
|
+
|
|
140
|
+
Lower-level factory for full control over sinks, sampling, and redaction.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
interface ObservabilityOptions {
|
|
144
|
+
serviceName: string;
|
|
145
|
+
serviceVersion?: string;
|
|
146
|
+
environment?: string;
|
|
147
|
+
sinks?: EventSink[];
|
|
148
|
+
sampleRate?: number;
|
|
149
|
+
redactKeys?: string[];
|
|
150
|
+
capturePayloads?: boolean;
|
|
151
|
+
tenantResolver?: (req: IncomingMessage) => string | undefined;
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `createTelemetryRouter()`
|
|
156
|
+
|
|
157
|
+
Express router providing telemetry endpoints:
|
|
158
|
+
|
|
159
|
+
| Endpoint | Method | Description |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| `/` | GET | JSON metrics data |
|
|
162
|
+
| `/dashboard` | GET | HTML dashboard with auto-refresh |
|
|
163
|
+
| `/event` | POST | UI event ingestion |
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
import { createTelemetryRouter } from '@listo-ai/mcp-observability';
|
|
167
|
+
|
|
168
|
+
app.use('/telemetry', createTelemetryRouter());
|
|
169
|
+
// Dashboard: http://localhost:3000/telemetry/dashboard
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### `expressTelemetry(observability)`
|
|
173
|
+
|
|
174
|
+
Express middleware for automatic HTTP request tracking.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { expressTelemetry } from '@listo-ai/mcp-observability';
|
|
178
|
+
|
|
179
|
+
app.use(expressTelemetry(observability));
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### `observability.wrapMcpHandler(requestKind, handler, context?)`
|
|
183
|
+
|
|
184
|
+
Wraps an MCP handler function for automatic invocation tracking.
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
server.setRequestHandler(
|
|
188
|
+
CallToolRequestSchema,
|
|
189
|
+
observability.wrapMcpHandler(
|
|
190
|
+
'CallTool',
|
|
191
|
+
async (request) => {
|
|
192
|
+
// Your tool implementation
|
|
193
|
+
},
|
|
194
|
+
(request) => ({
|
|
195
|
+
toolName: request.params.name,
|
|
196
|
+
sessionId: request.params.sessionId,
|
|
197
|
+
})
|
|
198
|
+
)
|
|
199
|
+
);
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `observability.recordBusinessEvent(name, properties?, status?)`
|
|
203
|
+
|
|
204
|
+
Record custom business events for domain-specific analytics.
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
observability.recordBusinessEvent('hotel_search', {
|
|
208
|
+
q: 'paris hotels',
|
|
209
|
+
minRating: 4.5,
|
|
210
|
+
total: 42,
|
|
211
|
+
}, 'ok');
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `observability.recordUiEvent(event)`
|
|
215
|
+
|
|
216
|
+
Record UI interaction events from the browser.
|
|
217
|
+
|
|
218
|
+
```typescript
|
|
219
|
+
observability.recordUiEvent({
|
|
220
|
+
name: 'booking_clicked',
|
|
221
|
+
properties: { hotelId: 'h123', hotelName: 'Hotel Paris' },
|
|
222
|
+
sessionId: 'session-456',
|
|
223
|
+
tenantId: 'tenant-789',
|
|
224
|
+
});
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### `observability.recordSession(event)`
|
|
228
|
+
|
|
229
|
+
Record MCP session lifecycle events.
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
observability.recordSession({
|
|
233
|
+
type: 'mcp_session',
|
|
234
|
+
action: 'open',
|
|
235
|
+
sessionId: 'session-123',
|
|
236
|
+
});
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### `RemoteSink`
|
|
240
|
+
|
|
241
|
+
Standalone remote sink for custom configurations.
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { RemoteSink } from '@listo-ai/mcp-observability';
|
|
245
|
+
|
|
246
|
+
const sink = new RemoteSink({
|
|
247
|
+
endpoint: 'https://api.listoai.co/v1/events/batch',
|
|
248
|
+
apiKey: 'fuse_xxxxxxxxxxxxx',
|
|
249
|
+
batchSize: 50, // events per batch
|
|
250
|
+
flushIntervalMs: 5000, // flush every 5s
|
|
251
|
+
maxRetries: 3, // retry with exponential backoff
|
|
252
|
+
debug: false,
|
|
253
|
+
onError: (err, events) => console.error('Failed to send', err),
|
|
254
|
+
});
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Integration Guide
|
|
258
|
+
|
|
259
|
+
### Express.js
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
import express from 'express';
|
|
263
|
+
import {
|
|
264
|
+
createMcpObservabilityEasy,
|
|
265
|
+
createTelemetryRouter,
|
|
266
|
+
expressTelemetry,
|
|
267
|
+
} from '@listo-ai/mcp-observability';
|
|
268
|
+
|
|
269
|
+
const app = express();
|
|
270
|
+
const observability = createMcpObservabilityEasy({
|
|
271
|
+
serviceName: 'my-api',
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Track all HTTP requests
|
|
275
|
+
app.use(expressTelemetry(observability));
|
|
276
|
+
|
|
277
|
+
// Telemetry endpoints
|
|
278
|
+
app.use('/telemetry', createTelemetryRouter());
|
|
279
|
+
|
|
280
|
+
app.get('/hotels', (req, res) => {
|
|
281
|
+
// Automatically tracked
|
|
282
|
+
res.json({ hotels: [] });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
app.listen(3000);
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### MCP Server
|
|
289
|
+
|
|
290
|
+
```typescript
|
|
291
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
292
|
+
import {
|
|
293
|
+
createMcpObservabilityEasy,
|
|
294
|
+
createTelemetryRouter,
|
|
295
|
+
} from '@listo-ai/mcp-observability';
|
|
296
|
+
import express from 'express';
|
|
297
|
+
|
|
298
|
+
const app = express();
|
|
299
|
+
const observability = createMcpObservabilityEasy({
|
|
300
|
+
serviceName: 'hotel-search-mcp',
|
|
301
|
+
serviceVersion: '0.1.0',
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
app.use('/telemetry', createTelemetryRouter());
|
|
305
|
+
|
|
306
|
+
const server = new Server({
|
|
307
|
+
name: 'hotel-search',
|
|
308
|
+
version: '0.1.0',
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
server.setRequestHandler(
|
|
312
|
+
CallToolRequestSchema,
|
|
313
|
+
observability.wrapMcpHandler(
|
|
314
|
+
'CallTool',
|
|
315
|
+
async (request) => {
|
|
316
|
+
// Your tool implementation
|
|
317
|
+
},
|
|
318
|
+
(request) => ({ toolName: request.params.name })
|
|
319
|
+
)
|
|
320
|
+
);
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Event Types
|
|
324
|
+
|
|
325
|
+
The SDK captures five event types:
|
|
326
|
+
|
|
327
|
+
### HTTP Request Events
|
|
328
|
+
|
|
329
|
+
Automatically captured via `expressTelemetry()` middleware.
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
{
|
|
333
|
+
type: 'http_request',
|
|
334
|
+
method: 'GET',
|
|
335
|
+
path: '/hotels',
|
|
336
|
+
route: '/hotels/:city',
|
|
337
|
+
statusCode: 200,
|
|
338
|
+
status: 'ok',
|
|
339
|
+
latencyMs: 45.2,
|
|
340
|
+
tenantId: 'tenant-123',
|
|
341
|
+
}
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### MCP Request Events
|
|
345
|
+
|
|
346
|
+
Captured via `wrapMcpHandler()`.
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
{
|
|
350
|
+
type: 'mcp_request',
|
|
351
|
+
requestKind: 'CallTool',
|
|
352
|
+
toolName: 'search_hotels',
|
|
353
|
+
status: 'ok',
|
|
354
|
+
latencyMs: 234.5,
|
|
355
|
+
inputBytes: 156,
|
|
356
|
+
outputBytes: 2048,
|
|
357
|
+
}
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
### MCP Session Events
|
|
361
|
+
|
|
362
|
+
Recorded via `recordSession()`. Always captured regardless of sample rate.
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
{
|
|
366
|
+
type: 'mcp_session',
|
|
367
|
+
action: 'open', // or 'close'
|
|
368
|
+
sessionId: 'session-123',
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Business Events
|
|
373
|
+
|
|
374
|
+
Custom domain events via `recordBusinessEvent()`.
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
{
|
|
378
|
+
type: 'business_event',
|
|
379
|
+
name: 'hotel_search',
|
|
380
|
+
status: 'ok',
|
|
381
|
+
properties: { q: 'paris', results: 42 },
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### UI Events
|
|
386
|
+
|
|
387
|
+
Browser interaction events via `recordUiEvent()` or the `POST /telemetry/event` endpoint.
|
|
388
|
+
|
|
389
|
+
```typescript
|
|
390
|
+
{
|
|
391
|
+
type: 'ui_event',
|
|
392
|
+
name: 'booking_clicked',
|
|
393
|
+
sessionId: 'session-123',
|
|
394
|
+
properties: { hotelId: 'h456' },
|
|
395
|
+
}
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
## Environment Variables
|
|
399
|
+
|
|
400
|
+
| Variable | Required | Description |
|
|
401
|
+
| --- | --- | --- |
|
|
402
|
+
| `INSIGHTS_API_URL` | For remote | Listo Insights API endpoint (default: `https://api.listoai.co`) |
|
|
403
|
+
| `INSIGHTS_API_KEY` | For remote | API key for authentication (format: `fuse_xxxxxxxxxxxxx`) |
|
|
404
|
+
| `NODE_ENV` | No | `development` / `staging` / `production` -- controls defaults |
|
|
405
|
+
| `TELEMETRY_SAMPLE_RATE` | No | Override sampling percentage (0.0 - 1.0) |
|
|
406
|
+
| `TELEMETRY_CAPTURE_PAYLOADS` | No | Enable/disable payload capture |
|
|
407
|
+
|
|
408
|
+
## Security
|
|
409
|
+
|
|
410
|
+
### Payload Sanitization
|
|
411
|
+
|
|
412
|
+
The SDK automatically redacts sensitive keys from all captured payloads. Redaction is applied recursively up to 6 levels deep.
|
|
413
|
+
|
|
414
|
+
**Default redacted keys:** `password`, `token`, `apiKey`, `secret`, `authorization`
|
|
415
|
+
|
|
416
|
+
**Custom redaction:**
|
|
417
|
+
|
|
418
|
+
```typescript
|
|
419
|
+
const observability = createMcpObservability({
|
|
420
|
+
serviceName: 'my-api',
|
|
421
|
+
redactKeys: ['password', 'token', 'creditCard', 'ssn'],
|
|
422
|
+
});
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### Sampling
|
|
426
|
+
|
|
427
|
+
- Errors are **always captured** regardless of sample rate
|
|
428
|
+
- Session events are **always captured** regardless of sample rate
|
|
429
|
+
- All other events are sampled at the configured rate (default: 100% dev, 10% prod)
|
|
430
|
+
|
|
431
|
+
### Content Security Policy
|
|
432
|
+
|
|
433
|
+
The HTML dashboard sets a `Content-Security-Policy` header restricting resource loading to inline styles and scripts only. No external resources are loaded.
|
|
434
|
+
|
|
435
|
+
### Data Minimization
|
|
436
|
+
|
|
437
|
+
The SDK collects no PII fields. There are no `userId`, `userLocation`, or similar fields in any event type. User search text (`userQuery`) is truncated to 240 characters and SHA-256 hashed. Error messages are captured verbatim -- ensure your errors do not contain sensitive data.
|
|
438
|
+
|
|
439
|
+
```typescript
|
|
440
|
+
const observability = createMcpObservabilityEasy({
|
|
441
|
+
serviceName: 'my-api',
|
|
442
|
+
sampleRate: 0.01, // 1% sampling
|
|
443
|
+
});
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Documentation
|
|
447
|
+
|
|
448
|
+
Full Amplitude-style documentation is available in the [`docs/`](./docs/) directory:
|
|
449
|
+
|
|
450
|
+
| Page | Description |
|
|
451
|
+
| --- | --- |
|
|
452
|
+
| [Overview](./docs/index.md) | Architecture, quick start |
|
|
453
|
+
| [Installation](./docs/installation.md) | npm install, prerequisites, ESM-only note |
|
|
454
|
+
| [Configuration](./docs/configuration.md) | Both init methods, full config tables |
|
|
455
|
+
| [Track HTTP](./docs/track-http.md) | `expressTelemetry()` + `trackHttpRequest()` |
|
|
456
|
+
| [Wrap MCP Handlers](./docs/wrap-mcp-handlers.md) | `wrapMcpHandler()`, context fields |
|
|
457
|
+
| [Sessions](./docs/sessions.md) | `recordSession()`, always-capture behavior |
|
|
458
|
+
| [Business Events](./docs/business-events.md) | `recordBusinessEvent()` |
|
|
459
|
+
| [UI Events](./docs/ui-events.md) | `recordUiEvent()` + POST endpoint |
|
|
460
|
+
| [Sinks](./docs/sinks.md) | InMemorySink, RemoteSink, ConsoleSink, combineSinks |
|
|
461
|
+
| [Endpoints](./docs/endpoints.md) | Dashboard, JSON API, event ingestion |
|
|
462
|
+
| [Easy Setup](./docs/easy-setup.md) | `createMcpObservabilityEasy()` deep-dive |
|
|
463
|
+
| [Sampling & Privacy](./docs/sampling-privacy.md) | Sampling, redaction, data minimization |
|
|
464
|
+
| [Advanced](./docs/advanced.md) | Custom sinks, metrics API, roadmap |
|
|
465
|
+
| [Troubleshooting](./docs/troubleshooting.md) | Common issues, debug mode |
|
|
466
|
+
|
|
467
|
+
## Development
|
|
468
|
+
|
|
469
|
+
### Prerequisites
|
|
470
|
+
|
|
471
|
+
- Node.js >= 20
|
|
472
|
+
- npm >= 10
|
|
473
|
+
|
|
474
|
+
### Setup
|
|
475
|
+
|
|
476
|
+
```bash
|
|
477
|
+
git clone git@github.com:Listo-Labs-Ltd/mcp-observability.git
|
|
478
|
+
cd mcp-observability
|
|
479
|
+
npm install
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
### Scripts
|
|
483
|
+
|
|
484
|
+
| Command | Description |
|
|
485
|
+
| --- | --- |
|
|
486
|
+
| `npm run build` | Compile TypeScript to `dist/` |
|
|
487
|
+
| `npm run lint` | Run ESLint |
|
|
488
|
+
| `npm run lint:fix` | Run ESLint with auto-fix |
|
|
489
|
+
| `npm run format` | Format source files with Prettier |
|
|
490
|
+
| `npm run format:check` | Check formatting without writing |
|
|
491
|
+
| `npm test` | Run tests with Vitest |
|
|
492
|
+
| `npm run test:watch` | Run tests in watch mode |
|
|
493
|
+
| `npm run test:coverage` | Run tests with coverage report |
|
|
494
|
+
| `npm run clean` | Remove `dist/` directory |
|
|
495
|
+
|
|
496
|
+
### Making Changes
|
|
497
|
+
|
|
498
|
+
1. Create a feature branch from `main`:
|
|
499
|
+
```bash
|
|
500
|
+
git checkout -b feat/my-feature
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
2. Make your changes in `src/`.
|
|
504
|
+
|
|
505
|
+
3. Ensure code quality:
|
|
506
|
+
```bash
|
|
507
|
+
npm run lint
|
|
508
|
+
npm run format:check
|
|
509
|
+
npm test
|
|
510
|
+
npm run build
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
4. Open a pull request against `main`. CI will automatically run lint, test, and build checks.
|
|
514
|
+
|
|
515
|
+
### Project Structure
|
|
516
|
+
|
|
517
|
+
```
|
|
518
|
+
mcp-observability/
|
|
519
|
+
├── .github/
|
|
520
|
+
│ └── workflows/
|
|
521
|
+
│ ├── ci.yml # Lint, test, build on push/PR
|
|
522
|
+
│ └── publish.yml # Publish to npm on release
|
|
523
|
+
├── docs/ # Comprehensive SDK documentation
|
|
524
|
+
├── src/
|
|
525
|
+
│ ├── index.ts # Core SDK: types, classes, analytics
|
|
526
|
+
│ ├── endpoints.ts # Express router and middleware
|
|
527
|
+
│ ├── easy-setup.ts # Simplified configuration factory
|
|
528
|
+
│ └── remote-sink.ts # Remote event batching and transmission
|
|
529
|
+
├── package.json
|
|
530
|
+
├── tsconfig.json
|
|
531
|
+
├── eslint.config.js
|
|
532
|
+
├── vitest.config.ts
|
|
533
|
+
├── .prettierrc
|
|
534
|
+
├── .npmrc # npm registry config
|
|
535
|
+
└── .gitignore
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Source Files
|
|
539
|
+
|
|
540
|
+
| File | Description |
|
|
541
|
+
| --- | --- |
|
|
542
|
+
| `src/index.ts` | Core SDK -- event types, `McpObservability` class, `InMemorySink`, sampling, sanitization, analytics aggregation |
|
|
543
|
+
| `src/endpoints.ts` | `expressTelemetry()` middleware and `createTelemetryRouter()` for JSON metrics, HTML dashboard, and event ingestion |
|
|
544
|
+
| `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 Listo Insights API with retry logic |
|
|
546
|
+
|
|
547
|
+
## CI/CD and Publishing
|
|
548
|
+
|
|
549
|
+
### Continuous Integration
|
|
550
|
+
|
|
551
|
+
Every push to `main` and every pull request triggers the CI workflow (`.github/workflows/ci.yml`):
|
|
552
|
+
|
|
553
|
+
1. **Lint** -- Checks code formatting (Prettier) and linting rules (ESLint)
|
|
554
|
+
2. **Test** -- Runs the test suite on Node.js 20 and 22
|
|
555
|
+
3. **Build** -- Compiles TypeScript and verifies `dist/` output
|
|
556
|
+
|
|
557
|
+
### Publishing a New Version
|
|
558
|
+
|
|
559
|
+
The package is automatically published to [npm](https://www.npmjs.com/package/@listo-ai/mcp-observability) when a push to `main` contains a new version in `package.json`. The publish workflow (`.github/workflows/publish.yml`) runs the full lint/test/build pipeline, then:
|
|
560
|
+
|
|
561
|
+
1. Reads the version from `package.json`
|
|
562
|
+
2. Checks if a `v<version>` git tag already exists
|
|
563
|
+
3. If the version is new: creates the git tag, publishes to npm, and creates a GitHub Release with auto-generated notes
|
|
564
|
+
4. If the version already exists: skips publish (the CI checks still run)
|
|
565
|
+
|
|
566
|
+
To release a new version:
|
|
567
|
+
|
|
568
|
+
1. Bump the version in `package.json`:
|
|
569
|
+
```bash
|
|
570
|
+
npm version patch # or minor, major
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
2. Push to `main` (or merge a PR):
|
|
574
|
+
```bash
|
|
575
|
+
git push origin main
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
The tag, npm publish, and GitHub Release are all handled automatically by CI.
|
|
579
|
+
|
|
580
|
+
## Troubleshooting
|
|
581
|
+
|
|
582
|
+
### Local dashboard not showing
|
|
583
|
+
|
|
584
|
+
- Ensure `enableLocalDashboard: true` is set (default in dev)
|
|
585
|
+
- Visit `http://localhost:PORT/telemetry/dashboard`
|
|
586
|
+
- Confirm `createTelemetryRouter()` is mounted: `app.use('/telemetry', createTelemetryRouter())`
|
|
587
|
+
|
|
588
|
+
### Data not reaching Listo Insights
|
|
589
|
+
|
|
590
|
+
- Verify `INSIGHTS_API_KEY` is set and valid (starts with `fuse_`)
|
|
591
|
+
- Check `INSIGHTS_API_URL` is correct
|
|
592
|
+
- Look for `[Listo Insights]` warnings in server logs
|
|
593
|
+
- Generate a new API key at https://app.listoai.co/settings
|
|
594
|
+
|
|
595
|
+
### Performance overhead
|
|
596
|
+
|
|
597
|
+
- Use sampling: `sampleRate: 0.1` (10% of events)
|
|
598
|
+
- Disable payload capture: `capturePayloads: false`
|
|
599
|
+
- Increase batch size for remote sink: `batchSize: 100`
|
|
600
|
+
|
|
601
|
+
### Build errors after cloning
|
|
602
|
+
|
|
603
|
+
- Ensure Node.js >= 20 is installed
|
|
604
|
+
- Run `npm install` to install all dependencies
|
|
605
|
+
- Run `npm run build` to compile TypeScript
|
|
606
|
+
|
|
607
|
+
## License
|
|
608
|
+
|
|
609
|
+
MIT
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface EasySetupConfig {
|
|
2
|
+
serviceName: string;
|
|
3
|
+
serviceVersion?: string;
|
|
4
|
+
environment?: 'dev' | 'staging' | 'production';
|
|
5
|
+
insightsApiUrl?: string;
|
|
6
|
+
insightsApiKey?: string;
|
|
7
|
+
enableLocalDashboard?: boolean;
|
|
8
|
+
sampleRate?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create an observability instance with environment-based defaults.
|
|
12
|
+
*
|
|
13
|
+
* This is the easiest way to get started with Listo Insights.
|
|
14
|
+
* It automatically:
|
|
15
|
+
* - Configures RemoteSink if INSIGHTS_API_KEY is provided
|
|
16
|
+
* - Configures InMemorySink + local dashboard in development
|
|
17
|
+
* - Applies sensible defaults (sampling, redaction, etc.)
|
|
18
|
+
* - Auto-detects tenant/user from common headers
|
|
19
|
+
*
|
|
20
|
+
* @example
|
|
21
|
+
* ```typescript
|
|
22
|
+
* const observability = createMcpObservabilityEasy({
|
|
23
|
+
* serviceName: "my-mcp-server",
|
|
24
|
+
* serviceVersion: "1.0.0",
|
|
25
|
+
* });
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Environment variables used:
|
|
29
|
+
* - INSIGHTS_API_URL: Insights API endpoint (default: https://api.listoai.co)
|
|
30
|
+
* - INSIGHTS_API_KEY: API key for authentication (generated in dashboard)
|
|
31
|
+
* - NODE_ENV: Determines if dev/production mode
|
|
32
|
+
*/
|
|
33
|
+
export declare function createMcpObservabilityEasy(config: EasySetupConfig): import("./index.js").McpObservability;
|
|
34
|
+
//# sourceMappingURL=easy-setup.d.ts.map
|
|
@@ -0,0 +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,yCAsEjE"}
|