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.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/conformance/README.md +59 -0
- package/conformance/fixtures/composite-trigger.valid.json +70 -0
- package/conformance/fixtures/event-lineage.valid.json +26 -0
- package/conformance/reference-host.mjs +145 -0
- package/package.json +15 -3
- package/scripts/embedded-host-kit.mjs +4 -0
- package/scripts/host-conformance.d.mts +75 -0
- package/scripts/host-conformance.mjs +238 -0
- package/scripts/host-integration.d.mts +5 -0
- package/scripts/host-integration.mjs +23 -4
- package/scripts/lib/composite-event-consumer.mjs +28 -0
- package/scripts/lib/composite-wake-coordinator.mjs +64 -0
- package/scripts/lib/local-event-intelligence-runtime.mjs +39 -0
- package/scripts/lib/mcp-events-client.mjs +36 -0
- package/scripts/lib/observability.d.mts +77 -0
- package/scripts/lib/observability.mjs +134 -0
- package/scripts/lib/temporal-deadline-scheduler.mjs +10 -0
- package/scripts/lib/trigger-control-plane.mjs +26 -0
- package/scripts/lib/wake-retry-scheduler.mjs +10 -0
- package/server.json +2 -2
|
@@ -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.
|
|
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.
|
|
16
|
+
"version": "0.9.0",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|