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,172 @@
1
+ import {
2
+ mcpOccurrenceToCorrelatableEvent,
3
+ } from '../../dist/src/mcpEvents/consumer.js';
4
+
5
+ export class CompositeEventConsumer {
6
+ constructor({
7
+ store,
8
+ triggerEngine,
9
+ wakeCoordinator = null,
10
+ wakeCoordinators = null,
11
+ derivedEventCoordinator = null,
12
+ processor = null,
13
+ maxDerivedDepth = 16,
14
+ }) {
15
+ this.store = store;
16
+ this.triggerEngine = triggerEngine;
17
+ this.wakeCoordinator = wakeCoordinator;
18
+ this.wakeCoordinators = wakeCoordinators instanceof Map
19
+ ? wakeCoordinators
20
+ : new Map();
21
+ this.derivedEventCoordinator = derivedEventCoordinator;
22
+ this.processor = processor;
23
+ this.maxDerivedDepth = Math.max(1, Number(maxDerivedDepth) || 16);
24
+ }
25
+
26
+ coordinatorFor(runtime) {
27
+ const configured = this.wakeCoordinators.get(runtime);
28
+ if (configured) return configured;
29
+ return this.wakeCoordinator;
30
+ }
31
+
32
+ async ingestMcpOccurrence({
33
+ event,
34
+ serverId,
35
+ provider,
36
+ traceId,
37
+ }) {
38
+ const correlatable = await mcpOccurrenceToCorrelatableEvent(
39
+ event,
40
+ {
41
+ traceId,
42
+ ...(provider ? { provider } : {}),
43
+ ...(serverId ? { serverId } : {}),
44
+ },
45
+ );
46
+
47
+ return this.ingestCorrelatable(correlatable);
48
+ }
49
+
50
+ async ingestCorrelatable(event, context = {}) {
51
+ const derivedDepth = Number(context.derivedDepth ?? 0);
52
+ const results = await this.triggerEngine.ingest(event);
53
+ const deliveries = [];
54
+ const derivedEvents = [];
55
+
56
+ for (const result of results) {
57
+ if (!result.matched || !result.match) continue;
58
+
59
+ const definition = this.store.listTriggers().find(
60
+ (candidate) =>
61
+ candidate.triggerId === result.match.triggerId &&
62
+ candidate.version === result.match.triggerVersion,
63
+ );
64
+
65
+ if (!definition) {
66
+ deliveries.push({
67
+ triggerId: result.triggerId,
68
+ matchId: result.match.matchId,
69
+ wakeId: null,
70
+ status: 'trigger_definition_missing',
71
+ runtimeReceiptId: null,
72
+ });
73
+ continue;
74
+ }
75
+
76
+ if (definition.derivedEvent && this.derivedEventCoordinator) {
77
+ if (derivedDepth >= this.maxDerivedDepth) {
78
+ const error = new Error(
79
+ `Derived event chain exceeded max depth ${this.maxDerivedDepth}`,
80
+ );
81
+ error.code = 'DERIVED_EVENT_CHAIN_DEPTH_EXCEEDED';
82
+ throw error;
83
+ }
84
+
85
+ const emission = await this.derivedEventCoordinator.emitMatched(
86
+ result.match,
87
+ definition,
88
+ );
89
+ const derivedRecord = emission.record;
90
+ const summary = {
91
+ triggerId: result.triggerId,
92
+ matchId: result.match.matchId,
93
+ status: emission.status,
94
+ accepted: emission.accepted,
95
+ eventId: derivedRecord?.event.sourceEventId ?? null,
96
+ eventName: derivedRecord?.event.name ?? definition.derivedEvent.name,
97
+ nested: null,
98
+ };
99
+
100
+ if (derivedRecord) {
101
+ // Re-enter even on replay. Downstream match/event idempotency makes this
102
+ // restart-safe if the process crashed after persistence but before fan-out.
103
+ summary.nested = await this.ingestCorrelatable(
104
+ derivedRecord.event,
105
+ { derivedDepth: derivedDepth + 1 },
106
+ );
107
+
108
+ if (!definition.target) {
109
+ await this.triggerEngine.markDerivedEmitted(
110
+ result.match.matchId,
111
+ derivedRecord.event.sourceEventId,
112
+ );
113
+ }
114
+ }
115
+
116
+ derivedEvents.push(summary);
117
+ }
118
+
119
+ if (!definition.target) {
120
+ continue;
121
+ }
122
+
123
+ const coordinator = this.coordinatorFor(definition.target.runtime);
124
+ if (!coordinator) {
125
+ deliveries.push({
126
+ triggerId: result.triggerId,
127
+ matchId: result.match.matchId,
128
+ wakeId: null,
129
+ status: 'runtime_unconfigured',
130
+ runtimeReceiptId: null,
131
+ });
132
+ continue;
133
+ }
134
+
135
+ const delivery = await coordinator.deliverMatched(result.match);
136
+ const persistedWake = delivery.wake;
137
+
138
+ if (
139
+ persistedWake?.status === 'delivered' &&
140
+ persistedWake.runtimeReceiptId &&
141
+ this.processor
142
+ ) {
143
+ const handled = await this.processor.acknowledgeWake(
144
+ persistedWake.wakeId,
145
+ persistedWake.runtimeReceiptId,
146
+ );
147
+ deliveries.push({
148
+ triggerId: result.triggerId,
149
+ matchId: result.match.matchId,
150
+ wakeId: handled.wakeId,
151
+ status: handled.status,
152
+ runtimeReceiptId: handled.runtimeReceiptId ?? null,
153
+ });
154
+ } else {
155
+ deliveries.push({
156
+ triggerId: result.triggerId,
157
+ matchId: result.match.matchId,
158
+ wakeId: persistedWake?.wakeId ?? null,
159
+ status: delivery.status,
160
+ runtimeReceiptId: persistedWake?.runtimeReceiptId ?? null,
161
+ });
162
+ }
163
+ }
164
+
165
+ return {
166
+ event,
167
+ results,
168
+ deliveries,
169
+ derivedEvents,
170
+ };
171
+ }
172
+ }
@@ -0,0 +1,185 @@
1
+ import {
2
+ WakeRecordSchema,
3
+ sha256Hex,
4
+ } from '../../dist/src/intelligenceProtocol/index.js';
5
+ import {
6
+ buildGenericRuntimeWakePacket,
7
+ } from './generic-runtime-wake.mjs';
8
+
9
+ async function stableId(prefix, value) {
10
+ const hash = await sha256Hex(value);
11
+ return `${prefix}_${hash.slice(0, 24)}`;
12
+ }
13
+
14
+ export class CompositeWakeCoordinator {
15
+ constructor({
16
+ store,
17
+ triggerEngine,
18
+ deliverer = null,
19
+ packetBuilder = buildGenericRuntimeWakePacket,
20
+ now = () => new Date(),
21
+ }) {
22
+ this.store = store;
23
+ this.triggerEngine = triggerEngine;
24
+ this.deliverer = deliverer;
25
+ this.packetBuilder = packetBuilder;
26
+ this.now = now;
27
+ }
28
+
29
+ async deliverMatched(match) {
30
+ if (!match || (match.status !== 'matched' && match.status !== 'fired')) {
31
+ return { status: 'not_matched', wake: null };
32
+ }
33
+
34
+ if (match.status === 'fired') {
35
+ const existing = match.firedWakeId
36
+ ? this.store.latestWake(match.firedWakeId)
37
+ : null;
38
+ return {
39
+ status: 'already_fired',
40
+ wake: existing,
41
+ };
42
+ }
43
+
44
+ const definition = this.store.listTriggers().find(
45
+ (candidate) =>
46
+ candidate.triggerId === match.triggerId &&
47
+ candidate.version === match.triggerVersion,
48
+ );
49
+ if (!definition) {
50
+ throw new Error(
51
+ `Trigger definition not found for ${match.triggerId}@${match.triggerVersion}`,
52
+ );
53
+ }
54
+
55
+ if (!definition.target) {
56
+ return { status: 'no_runtime_target', wake: null };
57
+ }
58
+
59
+ const wakeId = await stableId(
60
+ 'wake',
61
+ `composite:${match.matchId}:${definition.target.runtime}:${definition.target.id}`,
62
+ );
63
+
64
+ const existing = this.store.latestWake(wakeId);
65
+ if (existing?.status === 'handled' || existing?.status === 'delivered') {
66
+ if (match.status !== 'fired') {
67
+ await this.triggerEngine.markFired(match.matchId, wakeId);
68
+ }
69
+ return {
70
+ status: existing.status === 'handled' ? 'handled' : 'wake_delivered',
71
+ wake: existing,
72
+ };
73
+ }
74
+
75
+ const queued = WakeRecordSchema.parse({
76
+ wakeId,
77
+ traceId: match.sourceEvents[0]?.traceId ?? match.matchId,
78
+ decisionId: null,
79
+ subscriptionId: `trigger:${match.triggerId}`,
80
+ sourceEventId: match.matchId,
81
+ createdAt: this.now().toISOString(),
82
+ target: definition.target,
83
+ status: 'queued',
84
+ });
85
+
86
+ if (!existing || existing.status === 'dead_letter') {
87
+ await this.store.appendWake(queued);
88
+ await this.store.appendAudit({
89
+ auditId: await stableId(
90
+ 'audit',
91
+ `${wakeId}:queued:${this.store.auditLength()}`,
92
+ ),
93
+ traceId: queued.traceId,
94
+ timestamp: this.now().toISOString(),
95
+ kind: 'wake.queued',
96
+ entityType: 'wake',
97
+ entityId: wakeId,
98
+ toState: 'wake_queued',
99
+ details: {
100
+ triggerMatchId: match.matchId,
101
+ triggerId: match.triggerId,
102
+ composite: true,
103
+ },
104
+ });
105
+ }
106
+
107
+ if (!this.deliverer) {
108
+ return { status: 'wake_queued', wake: queued };
109
+ }
110
+
111
+ try {
112
+ const packet = this.packetBuilder({
113
+ wakeId,
114
+ match,
115
+ definition,
116
+ });
117
+ const receipt = await this.deliverer(packet);
118
+
119
+ const delivered = WakeRecordSchema.parse({
120
+ ...queued,
121
+ createdAt: this.now().toISOString(),
122
+ status: 'delivered',
123
+ runtimeReceiptId: receipt.runtimeReceiptId,
124
+ });
125
+ await this.store.appendWake(delivered);
126
+ await this.store.appendAudit({
127
+ auditId: await stableId(
128
+ 'audit',
129
+ `${wakeId}:delivered:${this.store.auditLength()}`,
130
+ ),
131
+ traceId: delivered.traceId,
132
+ timestamp: this.now().toISOString(),
133
+ kind: 'wake.delivered',
134
+ entityType: 'wake',
135
+ entityId: wakeId,
136
+ fromState: 'wake_queued',
137
+ toState: 'wake_delivered',
138
+ details: {
139
+ triggerMatchId: match.matchId,
140
+ runtimeReceiptId: delivered.runtimeReceiptId,
141
+ duplicate: receipt.duplicate === true,
142
+ composite: true,
143
+ },
144
+ });
145
+
146
+ await this.triggerEngine.markFired(match.matchId, wakeId);
147
+
148
+ return {
149
+ status: 'wake_delivered',
150
+ wake: delivered,
151
+ packet,
152
+ };
153
+ } catch (error) {
154
+ const deadLetter = WakeRecordSchema.parse({
155
+ ...queued,
156
+ createdAt: this.now().toISOString(),
157
+ status: 'dead_letter',
158
+ });
159
+ await this.store.appendWake(deadLetter);
160
+ await this.store.appendAudit({
161
+ auditId: await stableId(
162
+ 'audit',
163
+ `${wakeId}:dead-letter:${this.store.auditLength()}`,
164
+ ),
165
+ traceId: deadLetter.traceId,
166
+ timestamp: this.now().toISOString(),
167
+ kind: 'wake.dead_letter',
168
+ entityType: 'wake',
169
+ entityId: wakeId,
170
+ fromState: 'wake_queued',
171
+ toState: 'dead_letter',
172
+ details: {
173
+ triggerMatchId: match.matchId,
174
+ message: error instanceof Error ? error.message : 'wake delivery failed',
175
+ composite: true,
176
+ },
177
+ });
178
+ return {
179
+ status: 'dead_letter',
180
+ wake: deadLetter,
181
+ error,
182
+ };
183
+ }
184
+ }
185
+ }
@@ -0,0 +1,210 @@
1
+ import {
2
+ sha256Hex,
3
+ } from '../../dist/src/intelligenceProtocol/index.js';
4
+
5
+ function canonicalize(value) {
6
+ if (Array.isArray(value)) {
7
+ return value
8
+ .map(canonicalize)
9
+ .sort((a, b) =>
10
+ JSON.stringify(a).localeCompare(JSON.stringify(b))
11
+ );
12
+ }
13
+ if (value && typeof value === 'object') {
14
+ return Object.fromEntries(
15
+ Object.keys(value)
16
+ .sort()
17
+ .map((key) => [key, canonicalize(value[key])]),
18
+ );
19
+ }
20
+ return value;
21
+ }
22
+
23
+ function sameProducer(left, right) {
24
+ return (
25
+ left.triggerId === right.triggerId &&
26
+ left.triggerVersion === right.triggerVersion
27
+ );
28
+ }
29
+
30
+ export class DerivedContractRegistry {
31
+ constructor({
32
+ store,
33
+ audit = null,
34
+ now = () => new Date(),
35
+ }) {
36
+ this.store = store;
37
+ this.audit = audit;
38
+ this.now = now;
39
+ }
40
+
41
+ async fingerprint(payloadSchema) {
42
+ return sha256Hex(canonicalize(payloadSchema ?? {}));
43
+ }
44
+
45
+ list(eventName) {
46
+ return this.store.listDerivedContracts(eventName);
47
+ }
48
+
49
+ get(eventName, contractVersion) {
50
+ return this.store.getDerivedContract(eventName, contractVersion);
51
+ }
52
+
53
+ async assertCompatible({
54
+ eventName,
55
+ contractVersion,
56
+ payloadSchema,
57
+ }) {
58
+ const canonicalSchema = canonicalize(payloadSchema ?? {});
59
+ const schemaFingerprint = await this.fingerprint(canonicalSchema);
60
+ const existing = this.get(eventName, contractVersion);
61
+ if (
62
+ existing &&
63
+ existing.schemaFingerprint !== schemaFingerprint
64
+ ) {
65
+ const error = new Error(
66
+ `Derived event contract conflict for ${eventName}@${contractVersion}`,
67
+ );
68
+ error.code = 'DERIVED_CONTRACT_SCHEMA_CONFLICT';
69
+ error.details = {
70
+ eventName,
71
+ contractVersion,
72
+ existingFingerprint: existing.schemaFingerprint,
73
+ rejectedFingerprint: schemaFingerprint,
74
+ };
75
+ throw error;
76
+ }
77
+ return {
78
+ payloadSchema: canonicalSchema,
79
+ schemaFingerprint,
80
+ existing,
81
+ };
82
+ }
83
+
84
+ async registerProducer({
85
+ eventName,
86
+ contractVersion,
87
+ payloadSchema,
88
+ producer,
89
+ }) {
90
+ const canonicalSchema = canonicalize(payloadSchema ?? {});
91
+ const schemaFingerprint = await this.fingerprint(canonicalSchema);
92
+ const existing = this.get(eventName, contractVersion);
93
+
94
+ if (
95
+ existing &&
96
+ existing.schemaFingerprint !== schemaFingerprint
97
+ ) {
98
+ await this.audit?.({
99
+ kind: 'derived_contract.conflict',
100
+ entityType: 'derived_contract',
101
+ entityId: `${eventName}@${contractVersion}`,
102
+ traceId: `derived-contract:${eventName}@${contractVersion}`,
103
+ details: {
104
+ eventName,
105
+ contractVersion,
106
+ existingFingerprint: existing.schemaFingerprint,
107
+ rejectedFingerprint: schemaFingerprint,
108
+ producer,
109
+ },
110
+ });
111
+
112
+ const error = new Error(
113
+ `Derived event contract conflict for ${eventName}@${contractVersion}`,
114
+ );
115
+ error.code = 'DERIVED_CONTRACT_SCHEMA_CONFLICT';
116
+ error.details = {
117
+ eventName,
118
+ contractVersion,
119
+ existingFingerprint: existing.schemaFingerprint,
120
+ rejectedFingerprint: schemaFingerprint,
121
+ };
122
+ throw error;
123
+ }
124
+
125
+ const producers = existing
126
+ ? [...existing.producers]
127
+ : [];
128
+ if (!producers.some((candidate) => sameProducer(candidate, producer))) {
129
+ producers.push({
130
+ triggerId: String(producer.triggerId),
131
+ triggerVersion: String(producer.triggerVersion),
132
+ });
133
+ }
134
+
135
+ const record = await this.store.putDerivedContract({
136
+ eventName,
137
+ contractVersion,
138
+ payloadSchema: canonicalSchema,
139
+ schemaFingerprint,
140
+ producers,
141
+ });
142
+
143
+ await this.audit?.({
144
+ kind: existing
145
+ ? 'derived_contract.producer_registered'
146
+ : 'derived_contract.created',
147
+ entityType: 'derived_contract',
148
+ entityId: `${eventName}@${contractVersion}`,
149
+ traceId: `derived-contract:${eventName}@${contractVersion}`,
150
+ details: {
151
+ eventName,
152
+ contractVersion,
153
+ schemaFingerprint,
154
+ producer,
155
+ producerCount: record.producers.length,
156
+ },
157
+ });
158
+
159
+ return {
160
+ status: existing ? 'producer_registered' : 'created',
161
+ record,
162
+ };
163
+ }
164
+
165
+ resolveConsumer({
166
+ eventName,
167
+ contractVersion,
168
+ }) {
169
+ const contracts = this.list(eventName);
170
+
171
+ if (contractVersion) {
172
+ const contract = contracts.find(
173
+ (candidate) =>
174
+ candidate.contractVersion === String(contractVersion),
175
+ );
176
+ if (!contract) {
177
+ const error = new Error(
178
+ `Unknown derived event contract ${eventName}@${contractVersion}`,
179
+ );
180
+ error.code = 'DERIVED_CONTRACT_NOT_FOUND';
181
+ throw error;
182
+ }
183
+ return contract;
184
+ }
185
+
186
+ if (contracts.length === 1) {
187
+ return contracts[0];
188
+ }
189
+
190
+ if (contracts.length === 0) {
191
+ const error = new Error(
192
+ `No derived event contract registered for ${eventName}`,
193
+ );
194
+ error.code = 'DERIVED_CONTRACT_NOT_FOUND';
195
+ throw error;
196
+ }
197
+
198
+ const error = new Error(
199
+ `Derived event consumer is ambiguous for ${eventName}; explicit contractVersion required`,
200
+ );
201
+ error.code = 'DERIVED_CONTRACT_AMBIGUOUS';
202
+ error.details = {
203
+ eventName,
204
+ availableVersions: contracts.map(
205
+ (contract) => contract.contractVersion,
206
+ ),
207
+ };
208
+ throw error;
209
+ }
210
+ }