mcp-event-intelligence 0.8.2 → 0.9.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.
@@ -0,0 +1,77 @@
1
+ export type EventIntelligenceObservabilityLevel =
2
+ | 'debug'
3
+ | 'info'
4
+ | 'warn'
5
+ | 'error';
6
+
7
+ export interface EventIntelligenceObservabilityEvent {
8
+ schema: 'event-intelligence.observability.v1' | string;
9
+ timestamp: string;
10
+ level: EventIntelligenceObservabilityLevel;
11
+ event: `ei.${string}`;
12
+ component: string;
13
+ traceId?: string;
14
+ scopeId?: string;
15
+ triggerId?: string;
16
+ matchId?: string;
17
+ wakeId?: string;
18
+ connectionId?: string;
19
+ serverId?: string;
20
+ eventName?: string;
21
+ status?: string;
22
+ attempt?: number;
23
+ nextAttemptAt?: string;
24
+ error?: {
25
+ name?: string;
26
+ code?: string;
27
+ message: string;
28
+ };
29
+ metadata?: Record<string, unknown>;
30
+ }
31
+
32
+ export type EventIntelligenceObservabilitySink =
33
+ | ((event: EventIntelligenceObservabilityEvent) => void | Promise<void>)
34
+ | {
35
+ emit(
36
+ event: EventIntelligenceObservabilityEvent,
37
+ ): void | Promise<void>;
38
+ };
39
+
40
+ export interface EventIntelligenceObservabilityEmitter {
41
+ readonly enabled: boolean;
42
+ emit(input: {
43
+ event: `ei.${string}`;
44
+ level?: EventIntelligenceObservabilityLevel;
45
+ timestamp?: string;
46
+ component?: string;
47
+ traceId?: string;
48
+ scopeId?: string;
49
+ triggerId?: string;
50
+ matchId?: string;
51
+ wakeId?: string;
52
+ connectionId?: string;
53
+ serverId?: string;
54
+ eventName?: string;
55
+ status?: string;
56
+ attempt?: number;
57
+ nextAttemptAt?: string;
58
+ error?: unknown;
59
+ metadata?: Record<string, unknown>;
60
+ }): Promise<boolean>;
61
+ }
62
+
63
+ export const EI_OBSERVABILITY_SCHEMA_VERSION: '1';
64
+
65
+ export function serializeObservabilityError(error: unknown): Readonly<{
66
+ name?: string;
67
+ code?: string;
68
+ message: string;
69
+ }>;
70
+
71
+ export function createObservabilityEmitter(
72
+ sink?: EventIntelligenceObservabilitySink | null,
73
+ options?: {
74
+ now?: () => Date;
75
+ component?: string;
76
+ },
77
+ ): EventIntelligenceObservabilityEmitter;
@@ -0,0 +1,134 @@
1
+ const LEVELS = new Set(['debug', 'info', 'warn', 'error']);
2
+ const SENSITIVE_KEY = /(authorization|cookie|credential|password|secret|token|payload|instruction|context|evidence|body|content|data)/i;
3
+
4
+ export const EI_OBSERVABILITY_SCHEMA_VERSION = '1';
5
+
6
+ function truncate(value, max = 1000) {
7
+ const text = String(value ?? '')
8
+ .replace(/Bearer\\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]')
9
+ .replace(
10
+ /((?:api[_-]?key|authorization|password|secret|token)\\s*[=:]\\s*)[^\\s,;]+/gi,
11
+ '$1[redacted]',
12
+ );
13
+ return text.length > max ? `${text.slice(0, max)}…` : text;
14
+ }
15
+
16
+ function sanitize(value, depth = 0) {
17
+ if (value == null || typeof value === 'boolean' || typeof value === 'number') {
18
+ return value;
19
+ }
20
+ if (typeof value === 'string') return truncate(value);
21
+ if (depth >= 3) return '[truncated]';
22
+ if (Array.isArray(value)) {
23
+ return value.slice(0, 20).map((item) => sanitize(item, depth + 1));
24
+ }
25
+ if (value instanceof Error) {
26
+ return {
27
+ name: truncate(value.name, 120),
28
+ message: truncate(value.message),
29
+ ...(value.code == null ? {} : { code: truncate(value.code, 200) }),
30
+ };
31
+ }
32
+ if (typeof value === 'object') {
33
+ const result = {};
34
+ for (const [key, item] of Object.entries(value).slice(0, 40)) {
35
+ if (SENSITIVE_KEY.test(key)) continue;
36
+ const sanitized = sanitize(item, depth + 1);
37
+ if (sanitized !== undefined) result[key] = sanitized;
38
+ }
39
+ return result;
40
+ }
41
+ return undefined;
42
+ }
43
+
44
+ function normalizeSink(input) {
45
+ if (input == null) return null;
46
+ if (typeof input === 'function') return { emit: input };
47
+ if (typeof input === 'object' && typeof input.emit === 'function') {
48
+ return input;
49
+ }
50
+ throw new Error('observability must be a function or { emit(event) }');
51
+ }
52
+
53
+ export function serializeObservabilityError(error) {
54
+ if (error instanceof Error) {
55
+ return Object.freeze({
56
+ name: truncate(error.name, 120),
57
+ message: truncate(error.message),
58
+ ...(error.code == null ? {} : { code: truncate(error.code, 200) }),
59
+ });
60
+ }
61
+ return Object.freeze({ message: truncate(error) });
62
+ }
63
+
64
+ /**
65
+ * Runtime-neutral, best-effort observability boundary.
66
+ *
67
+ * Async sinks are detached after invocation, so slow telemetry cannot stall
68
+ * event processing. Sink failures are intentionally swallowed: telemetry must never
69
+ * change Event Intelligence delivery semantics. Event payloads, continuation
70
+ * instructions, evidence and credentials are not accepted as first-class
71
+ * fields and sensitive keys in metadata are dropped defensively.
72
+ */
73
+ export function createObservabilityEmitter(
74
+ sinkInput,
75
+ { now = () => new Date(), component = 'event-intelligence' } = {},
76
+ ) {
77
+ const sink = normalizeSink(sinkInput);
78
+
79
+ return Object.freeze({
80
+ enabled: Boolean(sink),
81
+ async emit(input = {}) {
82
+ if (!sink) return false;
83
+
84
+ const event = String(input.event ?? '').trim();
85
+ if (!event.startsWith('ei.')) {
86
+ throw new Error('observability event must use the ei.* namespace');
87
+ }
88
+
89
+ const level = String(input.level ?? 'info').toLowerCase();
90
+ if (!LEVELS.has(level)) {
91
+ throw new Error(`unsupported observability level: ${level}`);
92
+ }
93
+
94
+ const record = {
95
+ schema: `event-intelligence.observability.v${EI_OBSERVABILITY_SCHEMA_VERSION}`,
96
+ timestamp: input.timestamp
97
+ ? String(input.timestamp)
98
+ : now().toISOString(),
99
+ level,
100
+ event,
101
+ component: String(input.component ?? component),
102
+ ...(input.traceId ? { traceId: String(input.traceId) } : {}),
103
+ ...(input.scopeId ? { scopeId: String(input.scopeId) } : {}),
104
+ ...(input.triggerId ? { triggerId: String(input.triggerId) } : {}),
105
+ ...(input.matchId ? { matchId: String(input.matchId) } : {}),
106
+ ...(input.wakeId ? { wakeId: String(input.wakeId) } : {}),
107
+ ...(input.connectionId ? { connectionId: String(input.connectionId) } : {}),
108
+ ...(input.serverId ? { serverId: String(input.serverId) } : {}),
109
+ ...(input.eventName ? { eventName: String(input.eventName) } : {}),
110
+ ...(input.status ? { status: String(input.status) } : {}),
111
+ ...(input.attempt != null ? { attempt: Number(input.attempt) } : {}),
112
+ ...(input.nextAttemptAt
113
+ ? { nextAttemptAt: String(input.nextAttemptAt) }
114
+ : {}),
115
+ ...(input.error
116
+ ? { error: serializeObservabilityError(input.error) }
117
+ : {}),
118
+ ...(input.metadata && typeof input.metadata === 'object'
119
+ ? { metadata: sanitize(input.metadata) }
120
+ : {}),
121
+ };
122
+
123
+ try {
124
+ const pending = sink.emit(Object.freeze(record));
125
+ if (pending && typeof pending.then === 'function') {
126
+ void Promise.resolve(pending).catch(() => {});
127
+ }
128
+ return true;
129
+ } catch {
130
+ return false;
131
+ }
132
+ },
133
+ });
134
+ }
@@ -4,11 +4,13 @@ export class TemporalDeadlineScheduler {
4
4
  compositeEventConsumer,
5
5
  now = () => new Date(),
6
6
  intervalMs = 1000,
7
+ observability = null,
7
8
  }) {
8
9
  this.store = store;
9
10
  this.compositeEventConsumer = compositeEventConsumer;
10
11
  this.now = now;
11
12
  this.intervalMs = Math.max(250, Number(intervalMs) || 1000);
13
+ this.observability = observability;
12
14
  this.timer = null;
13
15
  this.running = false;
14
16
  }
@@ -94,6 +96,14 @@ export class TemporalDeadlineScheduler {
94
96
  if (this.timer) return;
95
97
  this.timer = setInterval(() => {
96
98
  this.runDue().catch((error) => {
99
+ if (this.observability?.enabled) {
100
+ void this.observability.emit({
101
+ event: 'ei.scheduler.temporal_deadline_failed',
102
+ level: 'error',
103
+ error,
104
+ });
105
+ return;
106
+ }
97
107
  console.error(JSON.stringify({
98
108
  message: 'temporal_deadline_scheduler_error',
99
109
  error: error instanceof Error ? error.message : String(error),
@@ -152,10 +152,12 @@ export class TriggerControlPlane {
152
152
  store,
153
153
  triggerEngine,
154
154
  now = () => new Date(),
155
+ observability = null,
155
156
  }) {
156
157
  this.store = store;
157
158
  this.triggerEngine = triggerEngine;
158
159
  this.now = now;
160
+ this.observability = observability;
159
161
  this.contractRegistry = new DerivedContractRegistry({
160
162
  store,
161
163
  now,
@@ -1110,5 +1112,29 @@ export class TriggerControlPlane {
1110
1112
  entityId,
1111
1113
  details,
1112
1114
  });
1115
+
1116
+ const event =
1117
+ kind === 'event_source.registered'
1118
+ ? 'ei.source.registered'
1119
+ : kind.startsWith('trigger.')
1120
+ ? `ei.${kind}`
1121
+ : null;
1122
+ if (event) {
1123
+ await this.observability?.emit({
1124
+ event,
1125
+ level: 'info',
1126
+ traceId,
1127
+ ...(entityType === 'trigger' ? { triggerId: entityId } : {}),
1128
+ ...(details?.connectionId ? { connectionId: details.connectionId } : {}),
1129
+ ...(details?.serverId ? { serverId: details.serverId } : {}),
1130
+ ...(details?.eventName ? { eventName: details.eventName } : {}),
1131
+ metadata: {
1132
+ entityType,
1133
+ ...(details?.version ? { version: details.version } : {}),
1134
+ ...(details?.fromVersion ? { fromVersion: details.fromVersion } : {}),
1135
+ ...(details?.toVersion ? { toVersion: details.toVersion } : {}),
1136
+ },
1137
+ });
1138
+ }
1113
1139
  }
1114
1140
  }
@@ -4,11 +4,13 @@ export class WakeRetryScheduler {
4
4
  resolveCoordinator,
5
5
  intervalMs = 1000,
6
6
  now = () => new Date(),
7
+ observability = null,
7
8
  }) {
8
9
  this.store = store;
9
10
  this.resolveCoordinator = resolveCoordinator;
10
11
  this.intervalMs = Math.max(100, Number(intervalMs) || 1000);
11
12
  this.now = now;
13
+ this.observability = observability;
12
14
  this.timer = null;
13
15
  this.running = false;
14
16
  }
@@ -60,6 +62,14 @@ export class WakeRetryScheduler {
60
62
  if (this.timer) return;
61
63
  this.timer = setInterval(() => {
62
64
  this.runDue().catch((error) => {
65
+ if (this.observability?.enabled) {
66
+ void this.observability.emit({
67
+ event: 'ei.scheduler.wake_retry_failed',
68
+ level: 'error',
69
+ error,
70
+ });
71
+ return;
72
+ }
63
73
  console.error(JSON.stringify({
64
74
  message: 'wake_retry_scheduler_error',
65
75
  error: error instanceof Error ? error.message : String(error),
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.sarooo17/event-intelligence",
4
4
  "title": "MCP Event Intelligence",
5
5
  "description": "MCP-native CEP for durable agents: event-time patterns, semantic predicates, derived events.",
6
- "version": "0.8.2",
6
+ "version": "0.9.0",
7
7
  "repository": {
8
8
  "url": "https://github.com/sarooo17/event-intelligence",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "mcp-event-intelligence",
16
- "version": "0.8.2",
16
+ "version": "0.9.0",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },