mcp-event-intelligence 0.3.0 → 0.4.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 +40 -0
- package/README.md +26 -14
- package/SECURITY.md +11 -7
- package/bin/mcp-event-intelligence.mjs +2 -2
- package/dist/src/composite/matching.d.ts +1 -1
- package/dist/src/composite/matching.js +21 -1
- package/dist/src/intelligenceProtocol/schemas.d.ts +2 -0
- package/dist/src/intelligenceProtocol/schemas.js +1 -0
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +2 -0
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +1 -0
- package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +6 -0
- package/dist/src/intelligenceProtocol/triggerSchemas.js +3 -0
- package/dist/src/mcpEvents/consumer.d.ts +1 -0
- package/dist/src/mcpEvents/consumer.js +1 -0
- package/dist/src/mcpEvents/erpnextProvider.d.ts +0 -2
- package/dist/src/mcpEvents/erpnextProvider.js +0 -3
- package/dist/src/mcpEvents/provider.d.ts +24 -10
- package/dist/src/mcpEvents/provider.js +124 -55
- package/dist/src/mcpEvents/server.d.ts +3 -1
- package/dist/src/mcpEvents/server.js +20 -19
- package/dist/src/protocol/types.d.ts +1 -0
- package/package.json +1 -1
- package/scripts/host-integration.d.mts +40 -0
- package/scripts/lib/composite-event-consumer.mjs +2 -0
- package/scripts/lib/json-schema.mjs +134 -0
- package/scripts/lib/local-event-intelligence-runtime.mjs +1 -0
- package/scripts/lib/mcp-events-client.mjs +760 -253
- package/scripts/lib/persistent-event-store.mjs +68 -9
- package/scripts/lib/trigger-control-plane.mjs +26 -0
- package/scripts/lib/trigger-planner.mjs +23 -0
- package/scripts/mcp-stdio-server.mjs +2 -1
- package/server.json +2 -2
|
@@ -32,6 +32,22 @@ function scopeIdFromDirectoryName(name) {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
function canonicalStateValue(value) {
|
|
36
|
+
if (Array.isArray(value)) return value.map(canonicalStateValue);
|
|
37
|
+
if (value && typeof value === 'object') {
|
|
38
|
+
return Object.fromEntries(
|
|
39
|
+
Object.keys(value)
|
|
40
|
+
.sort()
|
|
41
|
+
.map((key) => [key, canonicalStateValue(value[key])]),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
return value;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function mcpClientStateKey(connectionId, eventName, args = {}) {
|
|
48
|
+
return `${String(connectionId)}::${String(eventName)}::${JSON.stringify(canonicalStateValue(args ?? {}))}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
async function readJsonLines(filePath) {
|
|
36
52
|
try {
|
|
37
53
|
const raw = await readFile(filePath, 'utf8');
|
|
@@ -263,6 +279,7 @@ export class PersistentEventStore {
|
|
|
263
279
|
const record = {
|
|
264
280
|
sequence: Number(raw.sequence),
|
|
265
281
|
serverId: String(raw.serverId),
|
|
282
|
+
subscriptionId: raw.subscriptionId ? String(raw.subscriptionId) : null,
|
|
266
283
|
event: McpEventOccurrenceSchema.parse(raw.event),
|
|
267
284
|
};
|
|
268
285
|
if (!Number.isInteger(record.sequence) || record.sequence < 1) {
|
|
@@ -270,7 +287,7 @@ export class PersistentEventStore {
|
|
|
270
287
|
}
|
|
271
288
|
this.#mcpOccurrences.push(record);
|
|
272
289
|
this.#mcpOccurrenceKeys.add(
|
|
273
|
-
`${record.serverId}:${record.event.eventId}`,
|
|
290
|
+
`${record.serverId}:${record.subscriptionId ?? ''}:${record.event.eventId}`,
|
|
274
291
|
);
|
|
275
292
|
}
|
|
276
293
|
this.#mcpOccurrences.sort((a, b) => a.sequence - b.sequence);
|
|
@@ -282,15 +299,27 @@ export class PersistentEventStore {
|
|
|
282
299
|
connectionId: String(raw.connectionId),
|
|
283
300
|
serverId: String(raw.serverId || raw.connectionId),
|
|
284
301
|
eventName: String(raw.eventName),
|
|
302
|
+
arguments:
|
|
303
|
+
raw.arguments && !Array.isArray(raw.arguments) && typeof raw.arguments === 'object'
|
|
304
|
+
? raw.arguments
|
|
305
|
+
: {},
|
|
306
|
+
subscriptionId: raw.subscriptionId ? String(raw.subscriptionId) : null,
|
|
307
|
+
deliveryMode: raw.deliveryMode ? String(raw.deliveryMode) : 'poll',
|
|
285
308
|
cursor: raw.cursor === null || raw.cursor === undefined
|
|
286
309
|
? null
|
|
287
310
|
: String(raw.cursor),
|
|
288
311
|
updatedAt: String(raw.updatedAt || new Date(0).toISOString()),
|
|
312
|
+
...(raw.nextPollAt ? { nextPollAt: String(raw.nextPollAt) } : {}),
|
|
289
313
|
...(raw.lastEventAt ? { lastEventAt: String(raw.lastEventAt) } : {}),
|
|
290
314
|
...(raw.lastError ? { lastError: String(raw.lastError) } : {}),
|
|
315
|
+
...(typeof raw.truncated === 'boolean' ? { truncated: raw.truncated } : {}),
|
|
291
316
|
};
|
|
292
317
|
this.#mcpClientStates.set(
|
|
293
|
-
|
|
318
|
+
mcpClientStateKey(
|
|
319
|
+
state.connectionId,
|
|
320
|
+
state.eventName,
|
|
321
|
+
state.arguments,
|
|
322
|
+
),
|
|
294
323
|
state,
|
|
295
324
|
);
|
|
296
325
|
}
|
|
@@ -776,13 +805,19 @@ export class PersistentEventStore {
|
|
|
776
805
|
.filter((record) => !matchId || record.matchId === matchId);
|
|
777
806
|
}
|
|
778
807
|
|
|
779
|
-
async appendMcpOccurrence(serverId, eventInput) {
|
|
808
|
+
async appendMcpOccurrence(serverId, eventInput, subscriptionIdInput = null) {
|
|
780
809
|
return this.#serialized(async () => {
|
|
781
810
|
const event = McpEventOccurrenceSchema.parse(eventInput);
|
|
782
|
-
const
|
|
811
|
+
const subscriptionId =
|
|
812
|
+
subscriptionIdInput === null || subscriptionIdInput === undefined
|
|
813
|
+
? null
|
|
814
|
+
: String(subscriptionIdInput);
|
|
815
|
+
const key =
|
|
816
|
+
`${serverId}:${subscriptionId ?? ''}:${event.eventId}`;
|
|
783
817
|
const existing = this.#mcpOccurrences.find(
|
|
784
818
|
(record) =>
|
|
785
819
|
record.serverId === serverId &&
|
|
820
|
+
(record.subscriptionId ?? null) === subscriptionId &&
|
|
786
821
|
record.event.eventId === event.eventId,
|
|
787
822
|
);
|
|
788
823
|
if (this.#mcpOccurrenceKeys.has(key)) {
|
|
@@ -794,7 +829,12 @@ export class PersistentEventStore {
|
|
|
794
829
|
}
|
|
795
830
|
|
|
796
831
|
const sequence = this.latestMcpEventSequence() + 1;
|
|
797
|
-
const record = {
|
|
832
|
+
const record = {
|
|
833
|
+
sequence,
|
|
834
|
+
serverId,
|
|
835
|
+
subscriptionId,
|
|
836
|
+
event,
|
|
837
|
+
};
|
|
798
838
|
this.#mcpOccurrenceKeys.add(key);
|
|
799
839
|
this.#mcpOccurrences.push(record);
|
|
800
840
|
await appendFile(
|
|
@@ -816,6 +856,7 @@ export class PersistentEventStore {
|
|
|
816
856
|
.map((record) => ({
|
|
817
857
|
sequence: record.sequence,
|
|
818
858
|
serverId: record.serverId,
|
|
859
|
+
subscriptionId: record.subscriptionId ?? null,
|
|
819
860
|
event: {
|
|
820
861
|
...record.event,
|
|
821
862
|
data: { ...record.event.data },
|
|
@@ -823,9 +864,9 @@ export class PersistentEventStore {
|
|
|
823
864
|
}));
|
|
824
865
|
}
|
|
825
866
|
|
|
826
|
-
getMcpClientState(connectionId, eventName) {
|
|
867
|
+
getMcpClientState(connectionId, eventName, args = {}) {
|
|
827
868
|
return this.#mcpClientStates.get(
|
|
828
|
-
|
|
869
|
+
mcpClientStateKey(connectionId, eventName, args),
|
|
829
870
|
) ?? null;
|
|
830
871
|
}
|
|
831
872
|
|
|
@@ -843,24 +884,42 @@ export class PersistentEventStore {
|
|
|
843
884
|
if (!connectionId || !eventName) {
|
|
844
885
|
throw new Error('MCP client state requires connectionId and eventName');
|
|
845
886
|
}
|
|
846
|
-
const
|
|
887
|
+
const args =
|
|
888
|
+
input.arguments && !Array.isArray(input.arguments) && typeof input.arguments === 'object'
|
|
889
|
+
? input.arguments
|
|
890
|
+
: {};
|
|
891
|
+
const previous = this.getMcpClientState(connectionId, eventName, args);
|
|
847
892
|
const state = {
|
|
848
893
|
connectionId,
|
|
849
894
|
serverId: String(input.serverId || previous?.serverId || connectionId),
|
|
850
895
|
eventName,
|
|
896
|
+
arguments: args,
|
|
897
|
+
subscriptionId:
|
|
898
|
+
input.subscriptionId
|
|
899
|
+
? String(input.subscriptionId)
|
|
900
|
+
: previous?.subscriptionId ?? null,
|
|
901
|
+
deliveryMode: String(input.deliveryMode || previous?.deliveryMode || 'poll'),
|
|
851
902
|
cursor: input.cursor === null || input.cursor === undefined
|
|
852
903
|
? null
|
|
853
904
|
: String(input.cursor),
|
|
854
905
|
updatedAt: new Date().toISOString(),
|
|
906
|
+
...(input.nextPollAt || previous?.nextPollAt
|
|
907
|
+
? { nextPollAt: String(input.nextPollAt || previous.nextPollAt) }
|
|
908
|
+
: {}),
|
|
855
909
|
...(input.lastEventAt || previous?.lastEventAt
|
|
856
910
|
? { lastEventAt: String(input.lastEventAt || previous.lastEventAt) }
|
|
857
911
|
: {}),
|
|
858
912
|
...(input.lastError
|
|
859
913
|
? { lastError: String(input.lastError) }
|
|
860
914
|
: {}),
|
|
915
|
+
...(typeof input.truncated === 'boolean'
|
|
916
|
+
? { truncated: input.truncated }
|
|
917
|
+
: typeof previous?.truncated === 'boolean'
|
|
918
|
+
? { truncated: previous.truncated }
|
|
919
|
+
: {}),
|
|
861
920
|
};
|
|
862
921
|
this.#mcpClientStates.set(
|
|
863
|
-
|
|
922
|
+
mcpClientStateKey(connectionId, eventName, args),
|
|
864
923
|
state,
|
|
865
924
|
);
|
|
866
925
|
await appendFile(
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
DerivedContractRegistry,
|
|
7
7
|
} from './derived-contract-registry.mjs';
|
|
8
|
+
import { assertJsonSchemaValue } from './json-schema.mjs';
|
|
8
9
|
|
|
9
10
|
function actorIdentity(actor) {
|
|
10
11
|
if (!actor || typeof actor !== 'object') {
|
|
@@ -66,6 +67,26 @@ function scalarSchemaForConstant(value) {
|
|
|
66
67
|
return {};
|
|
67
68
|
}
|
|
68
69
|
|
|
70
|
+
function assertSubscriptionArguments(source, args, purpose) {
|
|
71
|
+
const inputSchema = source?.inputSchema;
|
|
72
|
+
if (
|
|
73
|
+
!inputSchema ||
|
|
74
|
+
typeof inputSchema !== 'object' ||
|
|
75
|
+
Object.keys(inputSchema).length === 0
|
|
76
|
+
) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
assertJsonSchemaValue(inputSchema, args ?? {});
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const wrapped = new Error(
|
|
83
|
+
`${purpose} arguments are invalid for ${source.serverId}/${source.eventName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
84
|
+
);
|
|
85
|
+
wrapped.code = 'TRIGGER_SOURCE_ARGUMENTS_INVALID';
|
|
86
|
+
throw wrapped;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
69
90
|
function assertAdvertisedPath(source, path, purpose) {
|
|
70
91
|
const payloadSchema = source?.payloadSchema;
|
|
71
92
|
if (
|
|
@@ -695,6 +716,11 @@ export class TriggerControlPlane {
|
|
|
695
716
|
|
|
696
717
|
for (const clause of definition.clauses) {
|
|
697
718
|
const source = resolveSource(clause);
|
|
719
|
+
assertSubscriptionArguments(
|
|
720
|
+
source,
|
|
721
|
+
clause.arguments ?? {},
|
|
722
|
+
`Trigger clause ${clause.id}`,
|
|
723
|
+
);
|
|
698
724
|
for (const predicate of clause.where ?? []) {
|
|
699
725
|
assertAdvertisedPath(
|
|
700
726
|
source,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
parseTriggerPlanInput,
|
|
5
5
|
sha256Hex,
|
|
6
6
|
} from '../../dist/src/intelligenceProtocol/index.js';
|
|
7
|
+
import { assertJsonSchemaValue } from './json-schema.mjs';
|
|
7
8
|
|
|
8
9
|
function schemaNodeAtPath(schema, path) {
|
|
9
10
|
if (!schema || typeof schema !== 'object') return null;
|
|
@@ -25,9 +26,28 @@ function sourceSummary(source) {
|
|
|
25
26
|
serverId: source.serverId,
|
|
26
27
|
eventName: source.eventName,
|
|
27
28
|
description: source.description ?? null,
|
|
29
|
+
delivery: source.delivery ?? [],
|
|
30
|
+
inputSchema: source.inputSchema ?? {},
|
|
31
|
+
payloadSchema: source.payloadSchema ?? {},
|
|
28
32
|
};
|
|
29
33
|
}
|
|
30
34
|
|
|
35
|
+
function validateArgumentsAgainstSource(source, args) {
|
|
36
|
+
const schema = source.inputSchema;
|
|
37
|
+
if (!schema || typeof schema !== 'object' || Object.keys(schema).length === 0) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
assertJsonSchemaValue(schema, args ?? {});
|
|
42
|
+
} catch (error) {
|
|
43
|
+
throw sourceError(
|
|
44
|
+
'TRIGGER_PLAN_ARGUMENTS_INVALID',
|
|
45
|
+
`Subscription arguments do not match ${source.serverId}/${source.eventName}: ${error instanceof Error ? error.message : String(error)}`,
|
|
46
|
+
[source],
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
function sourceError(code, message, sources = []) {
|
|
32
52
|
const error = new Error(message);
|
|
33
53
|
error.code = code;
|
|
@@ -149,6 +169,7 @@ export class TriggerPlanner {
|
|
|
149
169
|
}
|
|
150
170
|
|
|
151
171
|
const source = matches[0];
|
|
172
|
+
validateArgumentsAgainstSource(source, event.arguments);
|
|
152
173
|
for (const predicate of event.where) {
|
|
153
174
|
validatePredicateAgainstSource(source, predicate, warnings);
|
|
154
175
|
}
|
|
@@ -158,6 +179,7 @@ export class TriggerPlanner {
|
|
|
158
179
|
id,
|
|
159
180
|
event: event.event,
|
|
160
181
|
serverId: source.serverId,
|
|
182
|
+
arguments: event.arguments,
|
|
161
183
|
where: event.where,
|
|
162
184
|
};
|
|
163
185
|
});
|
|
@@ -230,6 +252,7 @@ export class TriggerPlanner {
|
|
|
230
252
|
id: clause.id,
|
|
231
253
|
event: clause.event,
|
|
232
254
|
serverId: clause.serverId,
|
|
255
|
+
arguments: clause.arguments,
|
|
233
256
|
where: clause.where,
|
|
234
257
|
})),
|
|
235
258
|
withinMs: definition.withinMs,
|
|
@@ -71,6 +71,7 @@ const triggerPlanInputSchema = z.object({
|
|
|
71
71
|
id: z.string().min(1).optional(),
|
|
72
72
|
event: z.string().min(1),
|
|
73
73
|
serverId: z.string().min(1).optional(),
|
|
74
|
+
arguments: z.record(z.string(), z.unknown()).optional(),
|
|
74
75
|
where: z.array(predicateInputSchema).optional(),
|
|
75
76
|
})).min(1),
|
|
76
77
|
match: z.union([
|
|
@@ -451,7 +452,7 @@ export async function buildEventIntelligenceMcpServer({
|
|
|
451
452
|
const server = new McpServer(
|
|
452
453
|
{
|
|
453
454
|
name: 'mcp-event-intelligence',
|
|
454
|
-
version: '0.3.
|
|
455
|
+
version: '0.3.1',
|
|
455
456
|
},
|
|
456
457
|
{
|
|
457
458
|
instructions:
|
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": "Durable temporal event intelligence: composite triggers, derived events, and runtime wakeups.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.4.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.4.0",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
},
|