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,756 @@
1
+ import { createServer } from 'node:http';
2
+ import {
3
+ EVENT_INTELLIGENCE_COMPATIBILITY,
4
+ EVENT_INTELLIGENCE_PROTOCOL_VERSION,
5
+ EVENT_INTELLIGENCE_SCHEMA_VERSION,
6
+ } from '../dist/src/intelligenceProtocol/index.js';
7
+ import {
8
+ TypeSafeJevEvaluator,
9
+ } from '../dist/src/semantic/typesafeJevEvaluator.js';
10
+ import {
11
+ CompositeTriggerEngine,
12
+ } from '../dist/src/composite/engine.js';
13
+ import {
14
+ ExperimentalMcpEventsServer,
15
+ } from '../dist/src/mcpEvents/server.js';
16
+ import { EventProcessor } from './lib/event-processor.mjs';
17
+ import {
18
+ githubEventPassesStructuredFilter,
19
+ translateGitHubWebhook,
20
+ verifyGitHubWebhookSignature,
21
+ } from './lib/github-webhook.mjs';
22
+ import { createHttpWakeDeliverer } from './lib/http-wake-deliverer.mjs';
23
+ import {
24
+ CompositeWakeCoordinator,
25
+ } from './lib/composite-wake-coordinator.mjs';
26
+ import {
27
+ buildGenericRuntimeWakePacket,
28
+ createSignedRuntimeWakeDeliverer,
29
+ readRuntimeWakeTargets,
30
+ } from './lib/generic-runtime-wake.mjs';
31
+ import {
32
+ CompositeEventConsumer,
33
+ } from './lib/composite-event-consumer.mjs';
34
+ import {
35
+ DerivedEventCoordinator,
36
+ } from './lib/derived-event-coordinator.mjs';
37
+ import {
38
+ TriggerControlPlane,
39
+ } from './lib/trigger-control-plane.mjs';
40
+ import {
41
+ ingestGitHubMcpEvent,
42
+ registerGitHubMcpEvents,
43
+ } from './lib/github-mcp-events-adapter.mjs';
44
+ import { PersistentEventStore } from './lib/persistent-event-store.mjs';
45
+ import {
46
+ TriggerInspector,
47
+ simulateTrigger,
48
+ } from './lib/trigger-inspector.mjs';
49
+ import {
50
+ TemporalDeadlineScheduler,
51
+ } from './lib/temporal-deadline-scheduler.mjs';
52
+
53
+ const port = Number(process.env.PORT ?? 3000);
54
+ const dataDir = process.env.DATA_DIR ?? './data';
55
+ const environmentId = process.env.ENVIRONMENT_ID ?? 'local';
56
+ const authToken = process.env.SERVICE_AUTH_TOKEN ?? '';
57
+
58
+ const store = new PersistentEventStore(dataDir);
59
+ const counts = await store.init();
60
+
61
+ const evaluator = process.env.TYPESAFE_API_KEY
62
+ ? TypeSafeJevEvaluator.fromEnvironment(process.env)
63
+ : null;
64
+
65
+ const wakeDeliverer = createHttpWakeDeliverer(
66
+ process.env.RUNTIME_WAKE_URL ?? '',
67
+ );
68
+
69
+ const processor = new EventProcessor({
70
+ store,
71
+ evaluator,
72
+ wakeDeliverer,
73
+ });
74
+ const compositeTriggers = new CompositeTriggerEngine(store, evaluator);
75
+ const runtimeWakeTargets = readRuntimeWakeTargets(
76
+ process.env.RUNTIME_WAKE_TARGETS_JSON,
77
+ );
78
+ const wakeCoordinators = new Map(
79
+ [...runtimeWakeTargets].map(([runtime, target]) => [
80
+ runtime,
81
+ new CompositeWakeCoordinator({
82
+ store,
83
+ triggerEngine: compositeTriggers,
84
+ deliverer: createSignedRuntimeWakeDeliverer(target),
85
+ packetBuilder: buildGenericRuntimeWakePacket,
86
+ }),
87
+ ]),
88
+ );
89
+ const derivedEventCoordinator = new DerivedEventCoordinator({ store });
90
+ const compositeEventConsumer = new CompositeEventConsumer({
91
+ store,
92
+ triggerEngine: compositeTriggers,
93
+ wakeCoordinators,
94
+ derivedEventCoordinator,
95
+ processor,
96
+ maxDerivedDepth: Number(process.env.MAX_DERIVED_EVENT_DEPTH ?? 16),
97
+ });
98
+ const temporalScheduler = new TemporalDeadlineScheduler({
99
+ store,
100
+ compositeEventConsumer,
101
+ intervalMs: Number(process.env.TEMPORAL_TICK_MS ?? 1000),
102
+ });
103
+ temporalScheduler.start();
104
+
105
+ const triggerControl = new TriggerControlPlane({
106
+ store,
107
+ triggerEngine: compositeTriggers,
108
+ });
109
+ const triggerInspector = new TriggerInspector({ store });
110
+ const mcpEventsServer = new ExperimentalMcpEventsServer(store);
111
+ registerGitHubMcpEvents(mcpEventsServer);
112
+
113
+ function sendJson(response, status, value) {
114
+ response.writeHead(status, {
115
+ 'Content-Type': 'application/json',
116
+ 'Cache-Control': 'no-store',
117
+ });
118
+ response.end(JSON.stringify(value));
119
+ }
120
+
121
+ async function readRawBody(request, limit = 1024 * 1024) {
122
+ const chunks = [];
123
+ let size = 0;
124
+
125
+ for await (const chunk of request) {
126
+ size += chunk.length;
127
+ if (size > limit) throw new Error('Request body too large');
128
+ chunks.push(chunk);
129
+ }
130
+
131
+ return Buffer.concat(chunks);
132
+ }
133
+
134
+ function requireBearer(request, response) {
135
+ if (!authToken) {
136
+ sendJson(response, 503, {
137
+ error: 'SERVICE_AUTH_TOKEN_NOT_CONFIGURED',
138
+ });
139
+ return false;
140
+ }
141
+
142
+ if (request.headers.authorization !== `Bearer ${authToken}`) {
143
+ sendJson(response, 401, { error: 'UNAUTHORIZED' });
144
+ return false;
145
+ }
146
+
147
+ return true;
148
+ }
149
+
150
+ function githubSemanticCondition() {
151
+ const instruction = process.env.SEMANTIC_INSTRUCTION;
152
+ if (!instruction) return undefined;
153
+
154
+ return {
155
+ type: 'semantic_boolean',
156
+ instruction,
157
+ input: ['data.title', 'data.bodyPreview', 'data.labels'],
158
+ matchThreshold: Number(process.env.SEMANTIC_MATCH_THRESHOLD ?? 0.8),
159
+ rejectThreshold: Number(process.env.SEMANTIC_REJECT_THRESHOLD ?? 0.2),
160
+ uncertain: process.env.SEMANTIC_UNCERTAIN_POLICY ?? 'escalate',
161
+ };
162
+ }
163
+
164
+ function logProcessingResult(result) {
165
+ console.log(
166
+ JSON.stringify({
167
+ message: 'event_intelligence_result',
168
+ traceId: result.traceId,
169
+ sourceEventId: result.sourceEventId,
170
+ status: result.status,
171
+ decisionId: result.decision?.decisionId ?? null,
172
+ outcome: result.decision?.outcome ?? null,
173
+ probability: result.decision?.probability ?? null,
174
+ evaluator: result.decision?.evaluator ?? null,
175
+ providerEvidence: result.decision?.providerEvidence ?? null,
176
+ wakeId: result.wake?.wakeId ?? null,
177
+ wakeStatus: result.wake?.status ?? null,
178
+ }),
179
+ );
180
+ }
181
+
182
+ function githubContext() {
183
+ const targetId = process.env.RUNTIME_TARGET_ID;
184
+ if (!targetId) {
185
+ throw new Error('RUNTIME_TARGET_ID is required for GitHub webhook delivery');
186
+ }
187
+
188
+ return {
189
+ environmentId,
190
+ subscriptionId:
191
+ process.env.GITHUB_SUBSCRIPTION_ID ?? 'github-issues-default',
192
+ serverId: process.env.GITHUB_SERVER_ID ?? 'github-webhook-adapter',
193
+ transport: 'webhook',
194
+ provider: 'github',
195
+ target: {
196
+ runtime: process.env.RUNTIME_TARGET_RUNTIME ?? 'agent-runtime',
197
+ kind: process.env.RUNTIME_TARGET_KIND ?? 'session',
198
+ id: targetId,
199
+ },
200
+ };
201
+ }
202
+
203
+ const server = createServer(async (request, response) => {
204
+ try {
205
+ const url = new URL(
206
+ request.url ?? '/',
207
+ `http://${request.headers.host ?? 'localhost'}`,
208
+ );
209
+
210
+ if (request.method === 'GET' && url.pathname === '/healthz') {
211
+ return sendJson(response, 200, {
212
+ status: 'ok',
213
+ protocolVersion: EVENT_INTELLIGENCE_PROTOCOL_VERSION,
214
+ schemaVersion: EVENT_INTELLIGENCE_SCHEMA_VERSION,
215
+ });
216
+ }
217
+
218
+ if (request.method === 'GET' && url.pathname === '/readyz') {
219
+ const auditValid = await store.verifyAudit();
220
+ const semanticRequired = Boolean(process.env.SEMANTIC_INSTRUCTION);
221
+ const evaluatorConfigured = Boolean(evaluator);
222
+ const ready =
223
+ auditValid &&
224
+ (!semanticRequired || evaluatorConfigured);
225
+
226
+ return sendJson(response, ready ? 200 : 503, {
227
+ status: ready
228
+ ? 'ready'
229
+ : !auditValid
230
+ ? 'audit_invalid'
231
+ : 'semantic_evaluator_unavailable',
232
+ auditValid,
233
+ semanticRequired,
234
+ evaluatorConfigured,
235
+ wakeCallbackConfigured: Boolean(wakeDeliverer),
236
+ genericRuntimeWakeTargets: [...runtimeWakeTargets.keys()],
237
+ eventSources: store.listEventSources().length,
238
+ pendingTemporalDeadlines:
239
+ store.listTemporalDeadlines({ status: 'pending' }).length,
240
+ derivedEvents: store.listDerivedEvents().length,
241
+ derivedContracts: store.listDerivedContracts().length,
242
+ });
243
+ }
244
+
245
+ if (request.method === 'POST' && url.pathname === '/v1/providers/github/webhook') {
246
+ const raw = await readRawBody(request);
247
+ const secret = process.env.GITHUB_WEBHOOK_SECRET ?? '';
248
+ const signature = request.headers['x-hub-signature-256'];
249
+
250
+ if (
251
+ !verifyGitHubWebhookSignature(
252
+ raw,
253
+ typeof signature === 'string' ? signature : '',
254
+ secret,
255
+ )
256
+ ) {
257
+ return sendJson(response, 401, { error: 'INVALID_GITHUB_SIGNATURE' });
258
+ }
259
+
260
+ const payload = JSON.parse(raw.toString('utf8'));
261
+ const translated = translateGitHubWebhook({
262
+ eventName: String(request.headers['x-github-event'] ?? ''),
263
+ deliveryId: String(request.headers['x-github-delivery'] ?? ''),
264
+ payload,
265
+ });
266
+
267
+ if (translated.kind === 'ping') {
268
+ return sendJson(response, 200, { status: 'pong' });
269
+ }
270
+
271
+ if (translated.kind === 'ignored') {
272
+ return sendJson(response, 202, {
273
+ status: 'ignored',
274
+ reason: translated.reason,
275
+ });
276
+ }
277
+
278
+ const mcpReceipt = await ingestGitHubMcpEvent(
279
+ store,
280
+ translated.event,
281
+ );
282
+
283
+ console.log(JSON.stringify({
284
+ message: 'mcp_event_ingested',
285
+ serverId: 'github-mcp-events',
286
+ eventId: translated.event.eventId,
287
+ eventName: translated.event.name,
288
+ accepted: mcpReceipt.accepted,
289
+ sequence: mcpReceipt.sequence,
290
+ }));
291
+
292
+ let composite = null;
293
+ if (mcpReceipt.accepted) {
294
+ composite = await compositeEventConsumer.ingestMcpOccurrence({
295
+ event: translated.event,
296
+ serverId: 'github-mcp-events',
297
+ provider: 'github',
298
+ traceId: `mcp:github:${translated.event.eventId}`,
299
+ });
300
+
301
+ console.log(JSON.stringify({
302
+ message: 'mcp_composite_result',
303
+ eventId: translated.event.eventId,
304
+ eventName: translated.event.name,
305
+ results: composite.results.map((result) => ({
306
+ triggerId: result.triggerId,
307
+ matchId: result.match?.matchId ?? null,
308
+ status: result.match?.status ?? null,
309
+ matched: result.matched,
310
+ fired: result.fired,
311
+ })),
312
+ deliveries: composite.deliveries,
313
+ derivedEvents: composite.derivedEvents?.map((item) => ({
314
+ eventId: item.eventId,
315
+ eventName: item.eventName,
316
+ status: item.status,
317
+ })) ?? [],
318
+ }));
319
+ }
320
+
321
+ if (
322
+ !githubEventPassesStructuredFilter(translated.event, {
323
+ repository: process.env.GITHUB_REPOSITORY,
324
+ label: process.env.GITHUB_LABEL,
325
+ })
326
+ ) {
327
+ return sendJson(response, 202, {
328
+ status: 'filtered_structured',
329
+ });
330
+ }
331
+
332
+ const result = await processor.ingest({
333
+ event: translated.event,
334
+ context: githubContext(),
335
+ semanticCondition: githubSemanticCondition(),
336
+ wakeOnEscalation:
337
+ process.env.WAKE_ON_ESCALATION === 'true',
338
+ });
339
+
340
+ logProcessingResult(result);
341
+ return sendJson(response, 202, result);
342
+ }
343
+
344
+ if (request.method === 'POST' && url.pathname === '/mcp') {
345
+ if (!requireBearer(request, response)) return;
346
+
347
+ const protocolVersion = request.headers['mcp-protocol-version'];
348
+ if (
349
+ typeof protocolVersion === 'string' &&
350
+ protocolVersion !== '2026-07-28'
351
+ ) {
352
+ return sendJson(response, 400, {
353
+ jsonrpc: '2.0',
354
+ id: null,
355
+ error: {
356
+ code: -32022,
357
+ message: 'Unsupported MCP protocol version',
358
+ data: { supported: ['2026-07-28'] },
359
+ },
360
+ });
361
+ }
362
+
363
+ const raw = await readRawBody(request);
364
+ const rpc = JSON.parse(raw.toString('utf8'));
365
+ if (
366
+ rpc?.jsonrpc !== '2.0' ||
367
+ (typeof rpc.id !== 'string' && typeof rpc.id !== 'number') ||
368
+ typeof rpc.method !== 'string'
369
+ ) {
370
+ return sendJson(response, 400, {
371
+ jsonrpc: '2.0',
372
+ id: rpc?.id ?? null,
373
+ error: { code: -32600, message: 'Invalid Request' },
374
+ });
375
+ }
376
+
377
+ const methodHeader = request.headers['mcp-method'];
378
+ if (
379
+ typeof methodHeader === 'string' &&
380
+ methodHeader !== rpc.method
381
+ ) {
382
+ return sendJson(response, 400, {
383
+ jsonrpc: '2.0',
384
+ id: rpc.id,
385
+ error: {
386
+ code: -32600,
387
+ message: 'Mcp-Method header does not match JSON-RPC method',
388
+ },
389
+ });
390
+ }
391
+
392
+ const result = await mcpEventsServer.handleRequest(rpc);
393
+ return sendJson(response, 200, result);
394
+ }
395
+
396
+ if (url.pathname.startsWith('/v1/') && !requireBearer(request, response)) {
397
+ return;
398
+ }
399
+
400
+ if (
401
+ request.method === 'GET' &&
402
+ url.pathname === '/v1/derived-contracts'
403
+ ) {
404
+ return sendJson(response, 200, {
405
+ contracts: store.listDerivedContracts(
406
+ url.searchParams.get('eventName') ?? undefined,
407
+ ),
408
+ });
409
+ }
410
+
411
+ if (
412
+ request.method === 'GET' &&
413
+ url.pathname === '/v1/derived-events'
414
+ ) {
415
+ return sendJson(response, 200, {
416
+ events: store.listDerivedEvents({
417
+ ...(url.searchParams.get('triggerId')
418
+ ? { triggerId: url.searchParams.get('triggerId') }
419
+ : {}),
420
+ ...(url.searchParams.get('matchId')
421
+ ? { matchId: url.searchParams.get('matchId') }
422
+ : {}),
423
+ ...(url.searchParams.get('name')
424
+ ? { name: url.searchParams.get('name') }
425
+ : {}),
426
+ }),
427
+ });
428
+ }
429
+
430
+ if (
431
+ request.method === 'GET' &&
432
+ url.pathname.startsWith('/v1/inspector/triggers/')
433
+ ) {
434
+ const triggerId = decodeURIComponent(
435
+ url.pathname.slice('/v1/inspector/triggers/'.length),
436
+ );
437
+ return sendJson(response, 200, triggerInspector.inspect({
438
+ triggerId,
439
+ version: url.searchParams.get('version') ?? undefined,
440
+ matchId: url.searchParams.get('matchId') ?? undefined,
441
+ }));
442
+ }
443
+
444
+ if (
445
+ request.method === 'POST' &&
446
+ url.pathname === '/v1/inspector/simulate'
447
+ ) {
448
+ const raw = await readRawBody(request);
449
+ const body = JSON.parse(raw.toString('utf8'));
450
+ const result = await simulateTrigger({
451
+ definition: body.definition,
452
+ events: body.events,
453
+ until: body.until,
454
+ order: body.order,
455
+ });
456
+ return sendJson(response, 200, result);
457
+ }
458
+
459
+ if (
460
+ request.method === 'GET' &&
461
+ url.pathname === '/v1/temporal/deadlines'
462
+ ) {
463
+ return sendJson(response, 200, {
464
+ deadlines: store.listTemporalDeadlines({
465
+ ...(url.searchParams.get('triggerId')
466
+ ? { triggerId: url.searchParams.get('triggerId') }
467
+ : {}),
468
+ ...(url.searchParams.get('matchId')
469
+ ? { matchId: url.searchParams.get('matchId') }
470
+ : {}),
471
+ ...(url.searchParams.get('status')
472
+ ? { status: url.searchParams.get('status') }
473
+ : {}),
474
+ }),
475
+ });
476
+ }
477
+
478
+ if (
479
+ request.method === 'POST' &&
480
+ url.pathname === '/v1/temporal/deadlines/run-due'
481
+ ) {
482
+ return sendJson(response, 200, {
483
+ outcomes: await temporalScheduler.runDue(),
484
+ });
485
+ }
486
+
487
+ if (request.method === 'GET' && url.pathname === '/v1/event-sources') {
488
+ const connectionIds = url.searchParams.getAll('connectionId');
489
+ return sendJson(response, 200, {
490
+ eventSources: triggerControl.listEventSources({
491
+ ...(connectionIds.length ? { connectionIds } : {}),
492
+ }),
493
+ });
494
+ }
495
+
496
+ if (request.method === 'POST' && url.pathname === '/v1/event-sources') {
497
+ const raw = await readRawBody(request);
498
+ const body = JSON.parse(raw.toString('utf8'));
499
+ const source = await triggerControl.registerEventSource(
500
+ body.source,
501
+ body.actor,
502
+ );
503
+ return sendJson(response, 201, { source });
504
+ }
505
+
506
+ if (request.method === 'GET' && url.pathname === '/v1/control/triggers') {
507
+ const ownerType = url.searchParams.get('ownerType');
508
+ const ownerId = url.searchParams.get('ownerId');
509
+ const tenantId = url.searchParams.get('tenantId');
510
+ return sendJson(response, 200, {
511
+ triggers: triggerControl.listTriggers({
512
+ ...(ownerType && ownerId
513
+ ? {
514
+ owner: {
515
+ type: ownerType,
516
+ principal_id: ownerId,
517
+ ...(tenantId ? { tenant_id: tenantId } : {}),
518
+ },
519
+ }
520
+ : {}),
521
+ }),
522
+ });
523
+ }
524
+
525
+ if (
526
+ request.method === 'PATCH' &&
527
+ url.pathname.startsWith('/v1/control/triggers/') &&
528
+ !url.pathname.endsWith('/pause') &&
529
+ !url.pathname.endsWith('/resume')
530
+ ) {
531
+ const triggerId = decodeURIComponent(
532
+ url.pathname.slice('/v1/control/triggers/'.length),
533
+ );
534
+ const raw = await readRawBody(request);
535
+ const body = JSON.parse(raw.toString('utf8'));
536
+ const result = await triggerControl.updateTrigger({
537
+ triggerId,
538
+ expectedVersion: String(body.expectedVersion || ''),
539
+ definition: body.definition,
540
+ connectionIds: body.connectionIds,
541
+ actor: body.actor,
542
+ owner: body.owner,
543
+ confirmationId: body.confirmationId,
544
+ });
545
+ return sendJson(response, 200, result);
546
+ }
547
+
548
+ if (
549
+ request.method === 'DELETE' &&
550
+ url.pathname.startsWith('/v1/control/triggers/')
551
+ ) {
552
+ const triggerId = decodeURIComponent(
553
+ url.pathname.slice('/v1/control/triggers/'.length),
554
+ );
555
+ const raw = await readRawBody(request);
556
+ const body = JSON.parse(raw.toString('utf8'));
557
+ const result = await triggerControl.deleteTrigger({
558
+ triggerId,
559
+ version: String(body.version || ''),
560
+ actor: body.actor,
561
+ owner: body.owner,
562
+ confirmationId: body.confirmationId,
563
+ });
564
+ return sendJson(response, 200, result);
565
+ }
566
+
567
+ if (request.method === 'POST' && url.pathname === '/v1/control/triggers') {
568
+ const raw = await readRawBody(request);
569
+ const body = JSON.parse(raw.toString('utf8'));
570
+ const result = await triggerControl.createTrigger({
571
+ definition: body.definition,
572
+ connectionIds: body.connectionIds,
573
+ actor: body.actor,
574
+ owner: body.owner,
575
+ confirmationId: body.confirmationId,
576
+ });
577
+ return sendJson(response, 201, result);
578
+ }
579
+
580
+ if (
581
+ request.method === 'POST' &&
582
+ url.pathname.startsWith('/v1/control/triggers/') &&
583
+ (url.pathname.endsWith('/pause') || url.pathname.endsWith('/resume'))
584
+ ) {
585
+ const action = url.pathname.endsWith('/pause') ? 'pause' : 'resume';
586
+ const suffix = action === 'pause' ? '/pause' : '/resume';
587
+ const triggerId = decodeURIComponent(
588
+ url.pathname.slice('/v1/control/triggers/'.length, -suffix.length),
589
+ );
590
+ const raw = await readRawBody(request);
591
+ const body = JSON.parse(raw.toString('utf8'));
592
+ const input = {
593
+ triggerId,
594
+ version: String(body.version || ''),
595
+ actor: body.actor,
596
+ owner: body.owner,
597
+ confirmationId: body.confirmationId,
598
+ };
599
+ const result = action === 'pause'
600
+ ? await triggerControl.pauseTrigger(input)
601
+ : await triggerControl.resumeTrigger(input);
602
+ return sendJson(response, 200, result);
603
+ }
604
+
605
+ if (request.method === 'POST' && url.pathname === '/v1/triggers') {
606
+ const raw = await readRawBody(request);
607
+ const parsed = JSON.parse(raw.toString('utf8'));
608
+ await triggerControl.assertDerivedOutputContract(parsed);
609
+ const definition = await compositeTriggers.register(parsed);
610
+ await triggerControl.registerDerivedOutputSource(definition);
611
+ return sendJson(response, 201, definition);
612
+ }
613
+
614
+ if (request.method === 'GET' && url.pathname === '/v1/triggers') {
615
+ return sendJson(response, 200, {
616
+ triggers: store.listTriggers(),
617
+ });
618
+ }
619
+
620
+ if (request.method === 'GET' && url.pathname === '/v1/trigger-matches') {
621
+ const triggerId = url.searchParams.get('triggerId') ?? undefined;
622
+ return sendJson(response, 200, {
623
+ matches: store.listTriggerMatches(triggerId),
624
+ });
625
+ }
626
+
627
+ if (
628
+ request.method === 'POST' &&
629
+ url.pathname === '/v1/composite/events/ingest'
630
+ ) {
631
+ const raw = await readRawBody(request);
632
+ const composite = await compositeEventConsumer.ingestCorrelatable(
633
+ JSON.parse(raw.toString('utf8')),
634
+ );
635
+ const { results, deliveries } = composite;
636
+
637
+ console.log(JSON.stringify({
638
+ message: 'composite_trigger_result',
639
+ results: results.map((result) => ({
640
+ triggerId: result.triggerId,
641
+ matchId: result.match?.matchId ?? null,
642
+ status: result.match?.status ?? null,
643
+ matched: result.matched,
644
+ fired: result.fired,
645
+ sourceEventIds:
646
+ result.match?.sourceEvents.map((event) => event.sourceEventId) ?? [],
647
+ })),
648
+ deliveries,
649
+ derivedEvents: composite.derivedEvents?.map((item) => ({
650
+ eventId: item.eventId,
651
+ eventName: item.eventName,
652
+ status: item.status,
653
+ })) ?? [],
654
+ }));
655
+ return sendJson(response, 202, { results, deliveries });
656
+ }
657
+
658
+ if (
659
+ request.method === 'POST' &&
660
+ url.pathname.startsWith('/v1/trigger-matches/') &&
661
+ url.pathname.endsWith('/fire')
662
+ ) {
663
+ const matchId = decodeURIComponent(
664
+ url.pathname.slice(
665
+ '/v1/trigger-matches/'.length,
666
+ -'/fire'.length,
667
+ ),
668
+ );
669
+ const raw = await readRawBody(request);
670
+ const body = raw.length ? JSON.parse(raw.toString('utf8')) : {};
671
+ if (typeof body.wakeId !== 'string' || !body.wakeId) {
672
+ return sendJson(response, 400, { error: 'wakeId is required' });
673
+ }
674
+ const fired = await compositeTriggers.markFired(matchId, body.wakeId);
675
+ return sendJson(response, 200, fired);
676
+ }
677
+
678
+ if (request.method === 'GET' && url.pathname === '/v1/protocol') {
679
+ return sendJson(response, 200, {
680
+ protocolVersion: EVENT_INTELLIGENCE_PROTOCOL_VERSION,
681
+ schemaVersion: EVENT_INTELLIGENCE_SCHEMA_VERSION,
682
+ compatibility: EVENT_INTELLIGENCE_COMPATIBILITY,
683
+ environmentId,
684
+ });
685
+ }
686
+
687
+ if (request.method === 'POST' && url.pathname === '/v1/events/ingest') {
688
+ const raw = await readRawBody(request);
689
+ const body = JSON.parse(raw.toString('utf8'));
690
+ const result = await processor.ingest(body);
691
+ logProcessingResult(result);
692
+ return sendJson(response, 202, result);
693
+ }
694
+
695
+ if (request.method === 'GET' && url.pathname === '/v1/audit') {
696
+ const afterSequence = Number(url.searchParams.get('afterSequence') ?? -1);
697
+ const traceId = url.searchParams.get('traceId') ?? undefined;
698
+ return sendJson(response, 200, {
699
+ records: store.listAudit({ traceId, afterSequence }),
700
+ verified: await store.verifyAudit(),
701
+ });
702
+ }
703
+
704
+ if (
705
+ request.method === 'GET' &&
706
+ url.pathname.startsWith('/v1/traces/')
707
+ ) {
708
+ const traceId = decodeURIComponent(
709
+ url.pathname.slice('/v1/traces/'.length),
710
+ );
711
+ return sendJson(response, 200, store.trace(traceId));
712
+ }
713
+
714
+ if (
715
+ request.method === 'POST' &&
716
+ url.pathname.startsWith('/v1/wakes/') &&
717
+ url.pathname.endsWith('/ack')
718
+ ) {
719
+ const wakeId = decodeURIComponent(
720
+ url.pathname.slice('/v1/wakes/'.length, -'/ack'.length),
721
+ );
722
+ const raw = await readRawBody(request);
723
+ const body = raw.length
724
+ ? JSON.parse(raw.toString('utf8'))
725
+ : {};
726
+ const handled = await processor.acknowledgeWake(
727
+ wakeId,
728
+ typeof body.runtimeReceiptId === 'string'
729
+ ? body.runtimeReceiptId
730
+ : undefined,
731
+ );
732
+ return sendJson(response, 200, handled);
733
+ }
734
+
735
+ return sendJson(response, 404, { error: 'NOT_FOUND' });
736
+ } catch (error) {
737
+ console.error(error);
738
+ return sendJson(response, 400, {
739
+ error: error instanceof Error ? error.message : 'UNKNOWN_ERROR',
740
+ });
741
+ }
742
+ });
743
+
744
+ server.listen(port, '0.0.0.0', () => {
745
+ console.log(
746
+ JSON.stringify({
747
+ message: 'mcp-event-intelligence service ready',
748
+ port,
749
+ dataDir,
750
+ environmentId,
751
+ restored: counts,
752
+ protocolVersion: EVENT_INTELLIGENCE_PROTOCOL_VERSION,
753
+ schemaVersion: EVENT_INTELLIGENCE_SCHEMA_VERSION,
754
+ }),
755
+ );
756
+ });