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,76 @@
1
+ import { createHmac } from 'node:crypto';
2
+ import { canonicalWakeJson } from './wake-signature.mjs';
3
+
4
+ export function buildGenericRuntimeWakePacket({ wakeId, match, definition }) {
5
+ if (!definition.target) {
6
+ throw new Error('Composite trigger has no runtime target');
7
+ }
8
+ return {
9
+ version: '1.0.0',
10
+ wake_id: wakeId,
11
+ trigger_match_id: match.matchId,
12
+ trigger_id: match.triggerId,
13
+ trigger_version: match.triggerVersion,
14
+ correlation_id: match.sourceEvents[0]?.traceId ?? match.matchId,
15
+ target: definition.target,
16
+ source_event_refs: match.sourceEvents.slice(-50).map((event) => ({
17
+ server_id: event.serverId ?? event.provider ?? 'event-intelligence',
18
+ event_id: event.sourceEventId,
19
+ event_name: event.eventName,
20
+ trace_id: event.traceId,
21
+ })),
22
+ matched_at: match.updatedAt,
23
+ };
24
+ }
25
+
26
+ export function createSignedRuntimeWakeDeliverer({ url, secret, fetchFn = fetch, now = () => Date.now() }) {
27
+ if (!url || !secret) return null;
28
+ return async (packet) => {
29
+ const timestamp = String(now());
30
+ const signature = createHmac('sha256', secret)
31
+ .update(`${timestamp}.${canonicalWakeJson(packet)}`)
32
+ .digest('hex');
33
+ const response = await fetchFn(url, {
34
+ method: 'POST',
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ 'X-Event-Wake-Timestamp': timestamp,
38
+ 'X-Event-Wake-Signature': `v1=${signature}`,
39
+ },
40
+ body: JSON.stringify(packet),
41
+ });
42
+ if (!response.ok) throw new Error(`Runtime wake callback failed: HTTP_${response.status}`);
43
+ const receipt = await response.json();
44
+ if (typeof receipt.runtimeReceiptId !== 'string' || !receipt.runtimeReceiptId) {
45
+ throw new Error('Runtime wake callback did not return runtimeReceiptId');
46
+ }
47
+ return {
48
+ runtimeReceiptId: receipt.runtimeReceiptId,
49
+ duplicate: receipt.duplicate === true,
50
+ status: receipt.status ?? 'completed',
51
+ };
52
+ };
53
+ }
54
+
55
+ export function readRuntimeWakeTargets(raw) {
56
+ if (!raw) return new Map();
57
+ const parsed = JSON.parse(raw);
58
+ if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
59
+ throw new Error('RUNTIME_WAKE_TARGETS_JSON must be an object');
60
+ }
61
+ const targets = new Map();
62
+ for (const [runtime, value] of Object.entries(parsed)) {
63
+ if (!runtime || !value || typeof value !== 'object') {
64
+ throw new Error('Invalid runtime wake target');
65
+ }
66
+ const url = new URL(value.url);
67
+ if (url.protocol !== 'https:' && !(url.protocol === 'http:' && ['localhost', '127.0.0.1'].includes(url.hostname))) {
68
+ throw new Error('Runtime wake target must use HTTPS or local HTTP');
69
+ }
70
+ if (typeof value.secret !== 'string' || value.secret.length < 32) {
71
+ throw new Error('Runtime wake target secret must have at least 32 characters');
72
+ }
73
+ targets.set(runtime, { url: url.toString(), secret: value.secret });
74
+ }
75
+ return targets;
76
+ }
@@ -0,0 +1,66 @@
1
+ const ISSUE_ACTIONS = ['opened', 'reopened', 'edited', 'closed'];
2
+
3
+ function descriptor(action) {
4
+ return {
5
+ name: `github.issue.${action}`,
6
+ description: `A GitHub issue was ${action}.`,
7
+ delivery: ['poll'],
8
+ inputSchema: {
9
+ type: 'object',
10
+ properties: {
11
+ repository: { type: 'string' },
12
+ label: { type: 'string' },
13
+ sender: { type: 'string' },
14
+ },
15
+ additionalProperties: false,
16
+ },
17
+ payloadSchema: {
18
+ type: 'object',
19
+ required: ['repository', 'number', 'title', 'labels', 'action'],
20
+ properties: {
21
+ repository: { type: 'string' },
22
+ number: { type: 'integer' },
23
+ title: { type: 'string' },
24
+ bodyPreview: { type: 'string' },
25
+ labels: { type: 'array', items: { type: 'string' } },
26
+ sender: { type: ['string', 'null'] },
27
+ action: { type: 'string' },
28
+ url: { type: ['string', 'null'] },
29
+ },
30
+ },
31
+ };
32
+ }
33
+
34
+ export function githubMcpEventDescriptors() {
35
+ return ISSUE_ACTIONS.map(descriptor);
36
+ }
37
+
38
+ export function githubMcpEventMatcher(args, event) {
39
+ if (args.repository && event.data.repository !== args.repository) return false;
40
+ if (args.sender && event.data.sender !== args.sender) return false;
41
+ if (
42
+ args.label &&
43
+ (!Array.isArray(event.data.labels) ||
44
+ !event.data.labels.includes(args.label))
45
+ ) {
46
+ return false;
47
+ }
48
+ return true;
49
+ }
50
+
51
+ export function registerGitHubMcpEvents(server) {
52
+ for (const eventDescriptor of githubMcpEventDescriptors()) {
53
+ server.registerEvent({
54
+ descriptor: eventDescriptor,
55
+ matcher: githubMcpEventMatcher,
56
+ });
57
+ }
58
+ }
59
+
60
+ export async function ingestGitHubMcpEvent(store, event) {
61
+ if (!ISSUE_ACTIONS.some((action) => event.name === `github.issue.${action}`)) {
62
+ throw new Error(`Unsupported GitHub MCP event: ${event.name}`);
63
+ }
64
+
65
+ return store.appendMcpOccurrence('github-mcp-events', event);
66
+ }
@@ -0,0 +1,88 @@
1
+ import { createHmac, timingSafeEqual } from 'node:crypto';
2
+
3
+ export function verifyGitHubWebhookSignature(rawBody, signature, secret) {
4
+ if (!secret || !signature?.startsWith('sha256=')) return false;
5
+
6
+ const expected = createHmac('sha256', secret)
7
+ .update(rawBody)
8
+ .digest('hex');
9
+ const actual = signature.slice('sha256='.length);
10
+
11
+ const expectedBuffer = Buffer.from(expected, 'hex');
12
+ const actualBuffer = Buffer.from(actual, 'hex');
13
+
14
+ if (expectedBuffer.length !== actualBuffer.length) return false;
15
+ return timingSafeEqual(expectedBuffer, actualBuffer);
16
+ }
17
+
18
+ export function translateGitHubWebhook({
19
+ eventName,
20
+ deliveryId,
21
+ payload,
22
+ }) {
23
+ if (eventName === 'ping') {
24
+ return { kind: 'ping' };
25
+ }
26
+
27
+ if (eventName !== 'issues') {
28
+ return { kind: 'ignored', reason: 'unsupported_event' };
29
+ }
30
+
31
+ const action = String(payload.action ?? '');
32
+ if (!['opened', 'reopened', 'edited', 'closed'].includes(action)) {
33
+ return { kind: 'ignored', reason: 'unsupported_action' };
34
+ }
35
+
36
+ const issue = payload.issue;
37
+ const repository = payload.repository;
38
+ if (!issue || !repository?.full_name || !deliveryId) {
39
+ throw new Error('Invalid GitHub issues webhook payload');
40
+ }
41
+
42
+ return {
43
+ kind: 'event',
44
+ event: {
45
+ eventId: deliveryId,
46
+ name: `github.issue.${action}`,
47
+ timestamp:
48
+ issue.updated_at ??
49
+ issue.created_at ??
50
+ new Date().toISOString(),
51
+ data: {
52
+ repository: repository.full_name,
53
+ number: issue.number,
54
+ title: issue.title ?? '',
55
+ bodyPreview:
56
+ typeof issue.body === 'string'
57
+ ? issue.body.slice(0, 1000)
58
+ : '',
59
+ labels: Array.isArray(issue.labels)
60
+ ? issue.labels
61
+ .map((label) =>
62
+ typeof label === 'string' ? label : label?.name,
63
+ )
64
+ .filter(Boolean)
65
+ : [],
66
+ sender: payload.sender?.login ?? null,
67
+ action,
68
+ url: issue.html_url ?? null,
69
+ },
70
+ cursor: null,
71
+ },
72
+ };
73
+ }
74
+
75
+ export function githubEventPassesStructuredFilter(
76
+ event,
77
+ { repository, label } = {},
78
+ ) {
79
+ if (repository && event.data.repository !== repository) return false;
80
+ if (
81
+ label &&
82
+ (!Array.isArray(event.data.labels) ||
83
+ !event.data.labels.includes(label))
84
+ ) {
85
+ return false;
86
+ }
87
+ return true;
88
+ }
@@ -0,0 +1,31 @@
1
+ export function createHttpWakeDeliverer(url, fetchFn = fetch) {
2
+ if (!url) return null;
3
+
4
+ return async (envelope) => {
5
+ const response = await fetchFn(url, {
6
+ method: 'POST',
7
+ headers: {
8
+ 'Content-Type': 'application/json',
9
+ },
10
+ body: JSON.stringify(envelope),
11
+ });
12
+
13
+ if (!response.ok) {
14
+ throw new Error(`Runtime wake callback failed with HTTP ${response.status}`);
15
+ }
16
+
17
+ let body = {};
18
+ try {
19
+ body = await response.json();
20
+ } catch {
21
+ // A 2xx response without JSON is still a valid acceptance.
22
+ }
23
+
24
+ return {
25
+ runtimeReceiptId:
26
+ typeof body.runtimeReceiptId === 'string'
27
+ ? body.runtimeReceiptId
28
+ : undefined,
29
+ };
30
+ };
31
+ }
@@ -0,0 +1,193 @@
1
+ import {
2
+ TypeSafeJevEvaluator,
3
+ } from '../../dist/src/semantic/typesafeJevEvaluator.js';
4
+ import {
5
+ CompositeTriggerEngine,
6
+ } from '../../dist/src/composite/engine.js';
7
+ import {
8
+ CompositeWakeCoordinator,
9
+ } from './composite-wake-coordinator.mjs';
10
+ import {
11
+ buildGenericRuntimeWakePacket,
12
+ createSignedRuntimeWakeDeliverer,
13
+ readRuntimeWakeTargets,
14
+ } from './generic-runtime-wake.mjs';
15
+ import {
16
+ CompositeEventConsumer,
17
+ } from './composite-event-consumer.mjs';
18
+ import {
19
+ DerivedEventCoordinator,
20
+ } from './derived-event-coordinator.mjs';
21
+ import {
22
+ TriggerControlPlane,
23
+ } from './trigger-control-plane.mjs';
24
+ import {
25
+ PersistentEventStore,
26
+ } from './persistent-event-store.mjs';
27
+ import {
28
+ TriggerInspector,
29
+ } from './trigger-inspector.mjs';
30
+ import {
31
+ TemporalDeadlineScheduler,
32
+ } from './temporal-deadline-scheduler.mjs';
33
+ import {
34
+ McpEventsClientManager,
35
+ } from './mcp-events-client.mjs';
36
+
37
+ export async function createLocalEventIntelligenceRuntime({
38
+ env = process.env,
39
+ mcpEventConnections = [],
40
+ wakeHandlers = {},
41
+ wake,
42
+ semanticEvaluator,
43
+ } = {}) {
44
+ const dataDir = env.DATA_DIR ?? './data';
45
+ const store = new PersistentEventStore(dataDir);
46
+ const restored = await store.init();
47
+
48
+ const evaluator =
49
+ semanticEvaluator !== undefined
50
+ ? semanticEvaluator
51
+ : env.TYPESAFE_API_KEY
52
+ ? TypeSafeJevEvaluator.fromEnvironment(env)
53
+ : null;
54
+
55
+ const triggerEngine = new CompositeTriggerEngine(store, evaluator);
56
+
57
+ const wakeCoordinators = new Map();
58
+
59
+ const runtimeWakeTargets = readRuntimeWakeTargets(
60
+ env.RUNTIME_WAKE_TARGETS_JSON,
61
+ );
62
+ for (const [runtime, target] of runtimeWakeTargets) {
63
+ wakeCoordinators.set(
64
+ runtime,
65
+ new CompositeWakeCoordinator({
66
+ store,
67
+ triggerEngine,
68
+ deliverer: createSignedRuntimeWakeDeliverer(target),
69
+ packetBuilder: buildGenericRuntimeWakePacket,
70
+ }),
71
+ );
72
+ }
73
+
74
+ const hostWakeEntries =
75
+ wakeHandlers instanceof Map
76
+ ? [...wakeHandlers.entries()]
77
+ : Object.entries(wakeHandlers || {});
78
+
79
+ for (const [runtime, handler] of hostWakeEntries) {
80
+ if (typeof handler !== 'function') {
81
+ throw new Error(`Host wake handler for ${runtime} must be a function`);
82
+ }
83
+ wakeCoordinators.set(
84
+ runtime,
85
+ new CompositeWakeCoordinator({
86
+ store,
87
+ triggerEngine,
88
+ packetBuilder: buildGenericRuntimeWakePacket,
89
+ deliverer: async (packet) => {
90
+ const result = await handler(packet);
91
+ if (typeof result === 'string' && result) {
92
+ return { runtimeReceiptId: result };
93
+ }
94
+ if (
95
+ result &&
96
+ typeof result === 'object' &&
97
+ typeof result.runtimeReceiptId === 'string' &&
98
+ result.runtimeReceiptId
99
+ ) {
100
+ return result;
101
+ }
102
+ throw new Error(
103
+ `Host wake handler for ${runtime} must return runtimeReceiptId`,
104
+ );
105
+ },
106
+ }),
107
+ );
108
+ }
109
+
110
+ let wakeCoordinator = null;
111
+ if (wake !== undefined) {
112
+ if (typeof wake !== 'function') {
113
+ throw new Error('Host wake dispatcher must be a function');
114
+ }
115
+ wakeCoordinator = new CompositeWakeCoordinator({
116
+ store,
117
+ triggerEngine,
118
+ packetBuilder: buildGenericRuntimeWakePacket,
119
+ deliverer: async (packet) => {
120
+ const result = await wake(packet);
121
+ if (typeof result === 'string' && result) {
122
+ return { runtimeReceiptId: result };
123
+ }
124
+ if (
125
+ result &&
126
+ typeof result === 'object' &&
127
+ typeof result.runtimeReceiptId === 'string' &&
128
+ result.runtimeReceiptId
129
+ ) {
130
+ return result;
131
+ }
132
+ throw new Error('Host wake dispatcher must return runtimeReceiptId');
133
+ },
134
+ });
135
+ }
136
+
137
+ const derivedEventCoordinator = new DerivedEventCoordinator({ store });
138
+ const compositeEventConsumer = new CompositeEventConsumer({
139
+ store,
140
+ triggerEngine,
141
+ wakeCoordinator,
142
+ wakeCoordinators,
143
+ derivedEventCoordinator,
144
+ maxDerivedDepth: Number(env.MAX_DERIVED_EVENT_DEPTH ?? 16),
145
+ });
146
+
147
+ const temporalScheduler = new TemporalDeadlineScheduler({
148
+ store,
149
+ compositeEventConsumer,
150
+ intervalMs: Number(env.TEMPORAL_TICK_MS ?? 1000),
151
+ });
152
+ temporalScheduler.start();
153
+
154
+ const triggerControl = new TriggerControlPlane({
155
+ store,
156
+ triggerEngine,
157
+ });
158
+ const triggerInspector = new TriggerInspector({ store });
159
+
160
+ const mcpEventsClient = new McpEventsClientManager({
161
+ store,
162
+ compositeEventConsumer,
163
+ connections: mcpEventConnections,
164
+ registerEventSource: (source) =>
165
+ triggerControl.registerEventSource(
166
+ source,
167
+ {
168
+ type: 'system',
169
+ principal_id: 'event-intelligence:host-mcp-client',
170
+ },
171
+ ),
172
+ });
173
+
174
+ const discovery = await mcpEventsClient.discoverAll();
175
+ mcpEventsClient.start();
176
+
177
+ return {
178
+ restored,
179
+ store,
180
+ evaluator,
181
+ triggerEngine,
182
+ triggerControl,
183
+ triggerInspector,
184
+ compositeEventConsumer,
185
+ temporalScheduler,
186
+ mcpEventsClient,
187
+ discovery,
188
+ async close() {
189
+ temporalScheduler.stop();
190
+ mcpEventsClient.stop();
191
+ },
192
+ };
193
+ }