mcp-event-intelligence 0.1.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.
Files changed (63) hide show
  1. package/CHANGELOG.md +80 -0
  2. package/LICENSE +201 -0
  3. package/README.md +298 -0
  4. package/SECURITY.md +42 -0
  5. package/bin/mcp-event-intelligence.mjs +52 -0
  6. package/dist/src/composite/engine.d.ts +87 -0
  7. package/dist/src/composite/engine.js +667 -0
  8. package/dist/src/composite/temporal.d.ts +32 -0
  9. package/dist/src/composite/temporal.js +317 -0
  10. package/dist/src/intelligenceProtocol/audit.d.ts +19 -0
  11. package/dist/src/intelligenceProtocol/audit.js +56 -0
  12. package/dist/src/intelligenceProtocol/canonical.d.ts +2 -0
  13. package/dist/src/intelligenceProtocol/canonical.js +21 -0
  14. package/dist/src/intelligenceProtocol/index.d.ts +12 -0
  15. package/dist/src/intelligenceProtocol/index.js +8 -0
  16. package/dist/src/intelligenceProtocol/invariants.d.ts +3 -0
  17. package/dist/src/intelligenceProtocol/invariants.js +36 -0
  18. package/dist/src/intelligenceProtocol/lifecycle.d.ts +4 -0
  19. package/dist/src/intelligenceProtocol/lifecycle.js +24 -0
  20. package/dist/src/intelligenceProtocol/mapper.d.ts +14 -0
  21. package/dist/src/intelligenceProtocol/mapper.js +28 -0
  22. package/dist/src/intelligenceProtocol/schemas.d.ts +325 -0
  23. package/dist/src/intelligenceProtocol/schemas.js +182 -0
  24. package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +488 -0
  25. package/dist/src/intelligenceProtocol/triggerSchemas.js +379 -0
  26. package/dist/src/intelligenceProtocol/version.d.ts +7 -0
  27. package/dist/src/intelligenceProtocol/version.js +7 -0
  28. package/dist/src/mcpEvents/consumer.d.ts +7 -0
  29. package/dist/src/mcpEvents/consumer.js +14 -0
  30. package/dist/src/mcpEvents/server.d.ts +41 -0
  31. package/dist/src/mcpEvents/server.js +122 -0
  32. package/dist/src/protocol/types.d.ts +37 -0
  33. package/dist/src/protocol/types.js +1 -0
  34. package/dist/src/semantic/conditionEngine.d.ts +7 -0
  35. package/dist/src/semantic/conditionEngine.js +65 -0
  36. package/dist/src/semantic/types.d.ts +31 -0
  37. package/dist/src/semantic/types.js +1 -0
  38. package/dist/src/semantic/typesafeJevEvaluator.d.ts +23 -0
  39. package/dist/src/semantic/typesafeJevEvaluator.js +95 -0
  40. package/package.json +85 -0
  41. package/scripts/host-integration.d.mts +86 -0
  42. package/scripts/host-integration.mjs +216 -0
  43. package/scripts/lib/composite-event-consumer.mjs +172 -0
  44. package/scripts/lib/composite-wake-coordinator.mjs +185 -0
  45. package/scripts/lib/derived-contract-registry.mjs +210 -0
  46. package/scripts/lib/derived-event-coordinator.mjs +242 -0
  47. package/scripts/lib/event-processor.mjs +409 -0
  48. package/scripts/lib/generic-runtime-wake.mjs +76 -0
  49. package/scripts/lib/github-mcp-events-adapter.mjs +66 -0
  50. package/scripts/lib/github-webhook.mjs +88 -0
  51. package/scripts/lib/http-wake-deliverer.mjs +31 -0
  52. package/scripts/lib/local-event-intelligence-runtime.mjs +193 -0
  53. package/scripts/lib/mcp-events-client.mjs +519 -0
  54. package/scripts/lib/persistent-event-store.mjs +769 -0
  55. package/scripts/lib/temporal-deadline-scheduler.mjs +111 -0
  56. package/scripts/lib/trigger-control-plane.mjs +939 -0
  57. package/scripts/lib/trigger-inspector.mjs +631 -0
  58. package/scripts/lib/wake-signature.mjs +10 -0
  59. package/scripts/mcp-stdio-server.mjs +380 -0
  60. package/scripts/mcp-stdio.mjs +3 -0
  61. package/scripts/run-tests.mjs +51 -0
  62. package/scripts/service.mjs +756 -0
  63. package/server.json +68 -0
@@ -0,0 +1,519 @@
1
+ import * as z from 'zod/v4';
2
+ import {
3
+ McpEventOccurrenceSchema,
4
+ } from '../../dist/src/intelligenceProtocol/index.js';
5
+
6
+ const EVENTS_CAPABILITY = 'io.modelcontextprotocol.experimental/events';
7
+ const UnknownResultSchema = z.unknown();
8
+
9
+ function assertObject(value, message) {
10
+ if (!value || Array.isArray(value) || typeof value !== 'object') {
11
+ throw new Error(message);
12
+ }
13
+ return value;
14
+ }
15
+
16
+ function normalizePositiveInteger(value, fallback, min, max, name) {
17
+ const normalized = Number(value ?? fallback);
18
+ if (!Number.isInteger(normalized) || normalized < min || normalized > max) {
19
+ throw new Error(`${name} must be between ${min} and ${max}`);
20
+ }
21
+ return normalized;
22
+ }
23
+
24
+ function normalizeConnection(connection) {
25
+ assertObject(connection, 'Invalid host-managed MCP Events connection');
26
+ const connectionId = String(connection.connectionId || '').trim();
27
+ if (!connectionId) {
28
+ throw new Error('Host-managed MCP Events connectionId is required');
29
+ }
30
+ if (typeof connection.request !== 'function') {
31
+ throw new Error(
32
+ `Host-managed MCP Events connection ${connectionId} requires request(method, params)`,
33
+ );
34
+ }
35
+
36
+ return {
37
+ connectionId,
38
+ serverId: String(connection.serverId || `host-mcp:${connectionId}`),
39
+ request: connection.request,
40
+ identity: connection.identity ?? connection.request,
41
+ enabled: connection.enabled !== false,
42
+ pollIntervalMs: normalizePositiveInteger(
43
+ connection.pollIntervalMs,
44
+ 5000,
45
+ 1000,
46
+ 300000,
47
+ 'pollIntervalMs',
48
+ ),
49
+ maxEvents: normalizePositiveInteger(
50
+ connection.maxEvents,
51
+ 100,
52
+ 1,
53
+ 500,
54
+ 'maxEvents',
55
+ ),
56
+ };
57
+ }
58
+
59
+ /**
60
+ * Adapt an MCP client that is already connected and authorized by the host.
61
+ *
62
+ * Event Intelligence never receives the provider URL, OAuth token, API key,
63
+ * or transport. It reuses the host's existing client instance.
64
+ */
65
+ export function createHostMcpEventsConnection({
66
+ connectionId,
67
+ serverId,
68
+ client,
69
+ request,
70
+ enabled = true,
71
+ pollIntervalMs = 5000,
72
+ maxEvents = 100,
73
+ } = {}) {
74
+ const directRequest =
75
+ typeof request === 'function'
76
+ ? request
77
+ : client && typeof client.request === 'function'
78
+ ? async (method, params) => {
79
+ if (
80
+ method === 'server/discover' &&
81
+ typeof client.getServerCapabilities === 'function'
82
+ ) {
83
+ const capabilities = client.getServerCapabilities();
84
+ if (capabilities) return { capabilities };
85
+ }
86
+
87
+ const message = {
88
+ method,
89
+ ...(params === undefined ? {} : { params }),
90
+ };
91
+
92
+ // Events is currently an experimental/custom surface, so the
93
+ // official SDK requires an explicit result schema for those calls.
94
+ if (method === 'server/discover') {
95
+ return client.request(message);
96
+ }
97
+ return client.request(message, UnknownResultSchema);
98
+ }
99
+ : null;
100
+
101
+ return normalizeConnection({
102
+ connectionId,
103
+ serverId,
104
+ request: directRequest,
105
+ identity: client ?? request ?? directRequest,
106
+ enabled,
107
+ pollIntervalMs,
108
+ maxEvents,
109
+ });
110
+ }
111
+
112
+ function schemaTypes(schema) {
113
+ if (!schema || typeof schema !== 'object') return [];
114
+ if (Array.isArray(schema.type)) return schema.type.map(String);
115
+ return schema.type ? [String(schema.type)] : [];
116
+ }
117
+
118
+ export function assertJsonSchemaValue(schema, value, path = '$') {
119
+ if (!schema || typeof schema !== 'object') return;
120
+
121
+ if (Array.isArray(schema.enum) && !schema.enum.some((item) => Object.is(item, value))) {
122
+ throw new Error(`MCP event payload violates schema at ${path}: value not in enum`);
123
+ }
124
+
125
+ const types = schemaTypes(schema);
126
+ if (types.length) {
127
+ const matches = types.some((type) => {
128
+ if (type === 'null') return value === null;
129
+ if (type === 'array') return Array.isArray(value);
130
+ if (type === 'object') return value !== null && !Array.isArray(value) && typeof value === 'object';
131
+ if (type === 'integer') return Number.isInteger(value);
132
+ if (type === 'number') return typeof value === 'number' && Number.isFinite(value);
133
+ return typeof value === type;
134
+ });
135
+ if (!matches) {
136
+ throw new Error(
137
+ `MCP event payload violates schema at ${path}: expected ${types.join('|')}`,
138
+ );
139
+ }
140
+ }
141
+
142
+ if (value && !Array.isArray(value) && typeof value === 'object') {
143
+ const required = Array.isArray(schema.required) ? schema.required.map(String) : [];
144
+ for (const key of required) {
145
+ if (!(key in value)) {
146
+ throw new Error(
147
+ `MCP event payload violates schema at ${path}: missing required property ${key}`,
148
+ );
149
+ }
150
+ }
151
+ const properties = schema.properties && typeof schema.properties === 'object'
152
+ ? schema.properties
153
+ : {};
154
+ for (const [key, childSchema] of Object.entries(properties)) {
155
+ if (key in value) {
156
+ assertJsonSchemaValue(childSchema, value[key], `${path}.${key}`);
157
+ }
158
+ }
159
+ }
160
+
161
+ if (Array.isArray(value) && schema.items) {
162
+ value.forEach((item, index) =>
163
+ assertJsonSchemaValue(schema.items, item, `${path}[${index}]`)
164
+ );
165
+ }
166
+ }
167
+
168
+ export class McpEventsClientManager {
169
+ constructor({
170
+ store,
171
+ compositeEventConsumer,
172
+ registerEventSource,
173
+ connections = [],
174
+ now = () => new Date(),
175
+ }) {
176
+ this.store = store;
177
+ this.compositeEventConsumer = compositeEventConsumer;
178
+ this.registerEventSource = registerEventSource;
179
+ this.connections = new Map();
180
+ this.now = now;
181
+ this.descriptors = new Map();
182
+ this.intervals = new Map();
183
+ this.lastErrors = new Map();
184
+ this.started = false;
185
+
186
+ for (const connection of connections) {
187
+ this.addConnection(connection);
188
+ }
189
+ }
190
+
191
+ addConnection(input) {
192
+ const connection = normalizeConnection(input);
193
+ if (connection.enabled === false) return connection;
194
+ if (this.connections.has(connection.connectionId)) {
195
+ throw new Error(
196
+ `Host-managed MCP Events connection already attached: ${connection.connectionId}`,
197
+ );
198
+ }
199
+ this.connections.set(connection.connectionId, connection);
200
+ return connection;
201
+ }
202
+
203
+ async attachConnection(input, { discover = true } = {}) {
204
+ const connection = this.addConnection(input);
205
+ let events = [];
206
+ if (connection.enabled !== false && discover) {
207
+ events = await this.discoverConnection(connection.connectionId);
208
+ }
209
+ if (connection.enabled !== false && this.started) {
210
+ this.scheduleConnection(connection);
211
+ }
212
+ return {
213
+ connectionId: connection.connectionId,
214
+ events,
215
+ };
216
+ }
217
+
218
+ async detachConnection(connectionId) {
219
+ const timer = this.intervals.get(connectionId);
220
+ if (timer) clearInterval(timer);
221
+ this.intervals.delete(connectionId);
222
+ this.descriptors.delete(connectionId);
223
+ this.lastErrors.delete(connectionId);
224
+
225
+ const removed = this.connections.delete(connectionId);
226
+ if (removed) {
227
+ const sources = this.store.listEventSources({ connectionIds: [connectionId] });
228
+ for (const source of sources) {
229
+ if (source.enabled === false) continue;
230
+ await this.registerEventSource({
231
+ ...source,
232
+ enabled: false,
233
+ });
234
+ }
235
+ }
236
+ return removed;
237
+ }
238
+
239
+ async rpc(connection, method, params) {
240
+ try {
241
+ return await connection.request(method, params);
242
+ } catch (error) {
243
+ throw new Error(
244
+ `Host MCP ${connection.connectionId} ${method} failed: ${error instanceof Error ? error.message : String(error)}`,
245
+ );
246
+ }
247
+ }
248
+
249
+ async discoverConnection(connectionId) {
250
+ const connection = this.connections.get(connectionId);
251
+ if (!connection) {
252
+ throw new Error(`Unknown host-managed MCP Events connection: ${connectionId}`);
253
+ }
254
+
255
+ const discover = await this.rpc(connection, 'server/discover');
256
+ const capabilities = discover?.capabilities ?? {};
257
+ const experimental = capabilities?.experimental?.[EVENTS_CAPABILITY];
258
+ const extension = capabilities?.extensions?.[EVENTS_CAPABILITY];
259
+ const standardish = capabilities?.events;
260
+ if (!experimental && !extension && !standardish) {
261
+ const error = new Error(
262
+ `MCP server ${connectionId} does not advertise Events capability`,
263
+ );
264
+ error.code = 'MCP_EVENTS_CAPABILITY_UNAVAILABLE';
265
+ throw error;
266
+ }
267
+
268
+ const listed = await this.rpc(connection, 'events/list', {});
269
+ if (!Array.isArray(listed?.events)) {
270
+ throw new Error(`MCP server ${connectionId} returned invalid events/list`);
271
+ }
272
+
273
+ const descriptors = [];
274
+ for (const raw of listed.events) {
275
+ const descriptor = assertObject(raw, 'Invalid MCP event descriptor');
276
+ const name = String(descriptor.name || '');
277
+ if (!name) throw new Error('MCP event descriptor requires name');
278
+ const delivery = Array.isArray(descriptor.delivery)
279
+ ? descriptor.delivery.map(String)
280
+ : ['poll'];
281
+ const normalized = {
282
+ name,
283
+ description: String(descriptor.description || ''),
284
+ delivery,
285
+ inputSchema:
286
+ descriptor.inputSchema && typeof descriptor.inputSchema === 'object'
287
+ ? descriptor.inputSchema
288
+ : {},
289
+ payloadSchema:
290
+ descriptor.payloadSchema && typeof descriptor.payloadSchema === 'object'
291
+ ? descriptor.payloadSchema
292
+ : {},
293
+ };
294
+ descriptors.push(normalized);
295
+
296
+ const sourceId = `mcp:${connection.connectionId}:${name}`;
297
+ const existing = this.store.listEventSources()
298
+ .find((source) => source.sourceId === sourceId);
299
+ const same =
300
+ existing &&
301
+ existing.enabled !== false &&
302
+ existing.connectionId === connection.connectionId &&
303
+ existing.serverId === connection.serverId &&
304
+ existing.eventName === name &&
305
+ JSON.stringify(existing.delivery) === JSON.stringify(delivery) &&
306
+ JSON.stringify(existing.inputSchema || {}) === JSON.stringify(normalized.inputSchema) &&
307
+ JSON.stringify(existing.payloadSchema || {}) === JSON.stringify(normalized.payloadSchema);
308
+
309
+ if (!same) {
310
+ await this.registerEventSource({
311
+ sourceId,
312
+ connectionId: connection.connectionId,
313
+ serverId: connection.serverId,
314
+ eventName: name,
315
+ description: normalized.description,
316
+ delivery,
317
+ inputSchema: normalized.inputSchema,
318
+ payloadSchema: normalized.payloadSchema,
319
+ enabled: true,
320
+ experimental: Boolean(experimental || extension),
321
+ metadata: {
322
+ transport: 'host-managed-mcp',
323
+ hostManaged: true,
324
+ },
325
+ });
326
+ }
327
+ }
328
+
329
+ this.descriptors.set(connection.connectionId, descriptors);
330
+ this.lastErrors.delete(connection.connectionId);
331
+ return descriptors;
332
+ }
333
+
334
+ async discoverAll() {
335
+ const results = [];
336
+ for (const connection of this.connections.values()) {
337
+ try {
338
+ const events = await this.discoverConnection(connection.connectionId);
339
+ results.push({
340
+ connectionId: connection.connectionId,
341
+ status: 'ready',
342
+ events: events.map((event) => event.name),
343
+ });
344
+ } catch (error) {
345
+ const message = error instanceof Error ? error.message : String(error);
346
+ this.lastErrors.set(connection.connectionId, message);
347
+ results.push({
348
+ connectionId: connection.connectionId,
349
+ status: 'error',
350
+ error: message,
351
+ });
352
+ }
353
+ }
354
+ return results;
355
+ }
356
+
357
+ async pollSource(connection, descriptor) {
358
+ if (!descriptor.delivery.includes('poll')) {
359
+ return { eventName: descriptor.name, status: 'delivery_not_supported', accepted: 0 };
360
+ }
361
+
362
+ const current = this.store.getMcpClientState(
363
+ connection.connectionId,
364
+ descriptor.name,
365
+ );
366
+
367
+ try {
368
+ const result = await this.rpc(connection, 'events/poll', {
369
+ name: descriptor.name,
370
+ arguments: {},
371
+ cursor: current?.cursor ?? null,
372
+ maxEvents: connection.maxEvents,
373
+ });
374
+
375
+ if (typeof result?.cursor !== 'string' || !Array.isArray(result.events)) {
376
+ throw new Error(
377
+ `MCP server ${connection.connectionId} returned invalid events/poll`,
378
+ );
379
+ }
380
+
381
+ let accepted = 0;
382
+ let lastEventAt = current?.lastEventAt;
383
+ for (const raw of result.events) {
384
+ const occurrence = McpEventOccurrenceSchema.parse(raw);
385
+ if (occurrence.name !== descriptor.name) {
386
+ throw new Error(
387
+ `MCP event name mismatch: expected ${descriptor.name}, got ${occurrence.name}`,
388
+ );
389
+ }
390
+ assertJsonSchemaValue(descriptor.payloadSchema, occurrence.data);
391
+
392
+ const receipt = await this.store.appendMcpOccurrence(
393
+ connection.serverId,
394
+ occurrence,
395
+ );
396
+ if (!receipt.accepted) continue;
397
+
398
+ accepted += 1;
399
+ lastEventAt = occurrence.timestamp;
400
+ await this.compositeEventConsumer.ingestMcpOccurrence({
401
+ event: occurrence,
402
+ serverId: connection.serverId,
403
+ provider: 'mcp',
404
+ traceId: `mcp:${connection.serverId}:${occurrence.eventId}`,
405
+ });
406
+ }
407
+
408
+ await this.store.putMcpClientState({
409
+ connectionId: connection.connectionId,
410
+ serverId: connection.serverId,
411
+ eventName: descriptor.name,
412
+ cursor: result.cursor,
413
+ ...(lastEventAt ? { lastEventAt } : {}),
414
+ });
415
+ this.lastErrors.delete(connection.connectionId);
416
+
417
+ return {
418
+ eventName: descriptor.name,
419
+ status: 'ok',
420
+ accepted,
421
+ cursor: result.cursor,
422
+ hasMore: result.hasMore === true,
423
+ };
424
+ } catch (error) {
425
+ const message = error instanceof Error ? error.message : String(error);
426
+ this.lastErrors.set(connection.connectionId, message);
427
+ await this.store.putMcpClientState({
428
+ connectionId: connection.connectionId,
429
+ serverId: connection.serverId,
430
+ eventName: descriptor.name,
431
+ cursor: current?.cursor ?? null,
432
+ lastError: message,
433
+ });
434
+ throw error;
435
+ }
436
+ }
437
+
438
+ async pollConnection(connectionId) {
439
+ const connection = this.connections.get(connectionId);
440
+ if (!connection) {
441
+ throw new Error(`Unknown host-managed MCP Events connection: ${connectionId}`);
442
+ }
443
+
444
+ let descriptors = this.descriptors.get(connectionId);
445
+ if (!descriptors) {
446
+ descriptors = await this.discoverConnection(connectionId);
447
+ }
448
+
449
+ const results = [];
450
+ for (const descriptor of descriptors) {
451
+ results.push(await this.pollSource(connection, descriptor));
452
+ }
453
+ return results;
454
+ }
455
+
456
+ async pollAll() {
457
+ const results = [];
458
+ for (const connection of this.connections.values()) {
459
+ try {
460
+ results.push({
461
+ connectionId: connection.connectionId,
462
+ results: await this.pollConnection(connection.connectionId),
463
+ });
464
+ } catch (error) {
465
+ results.push({
466
+ connectionId: connection.connectionId,
467
+ error: error instanceof Error ? error.message : String(error),
468
+ });
469
+ }
470
+ }
471
+ return results;
472
+ }
473
+
474
+ scheduleConnection(connection) {
475
+ if (this.intervals.has(connection.connectionId)) return;
476
+ const timer = setInterval(() => {
477
+ this.pollConnection(connection.connectionId).catch((error) => {
478
+ console.error(JSON.stringify({
479
+ message: 'host_mcp_events_poll_error',
480
+ connectionId: connection.connectionId,
481
+ error: error instanceof Error ? error.message : String(error),
482
+ }));
483
+ });
484
+ }, connection.pollIntervalMs);
485
+ timer.unref?.();
486
+ this.intervals.set(connection.connectionId, timer);
487
+ }
488
+
489
+ start() {
490
+ this.started = true;
491
+ for (const connection of this.connections.values()) {
492
+ this.scheduleConnection(connection);
493
+ }
494
+ }
495
+
496
+ stop() {
497
+ this.started = false;
498
+ for (const timer of this.intervals.values()) clearInterval(timer);
499
+ this.intervals.clear();
500
+ }
501
+
502
+ status() {
503
+ return [...this.connections.values()].map((connection) => ({
504
+ connectionId: connection.connectionId,
505
+ serverId: connection.serverId,
506
+ hostManaged: true,
507
+ events: (this.descriptors.get(connection.connectionId) || [])
508
+ .map((event) => ({
509
+ name: event.name,
510
+ delivery: event.delivery,
511
+ cursor: this.store.getMcpClientState(
512
+ connection.connectionId,
513
+ event.name,
514
+ )?.cursor ?? null,
515
+ })),
516
+ error: this.lastErrors.get(connection.connectionId) ?? null,
517
+ }));
518
+ }
519
+ }