mcp-event-intelligence 0.4.0 → 0.5.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 +59 -0
- package/README.md +102 -14
- package/SECURITY.md +4 -4
- package/bin/mcp-event-intelligence.mjs +3 -3
- package/dist/src/composite/engine.d.ts +6 -0
- package/dist/src/composite/engine.js +599 -10
- package/dist/src/composite/matching.js +45 -0
- package/dist/src/intelligenceProtocol/activationSchemas.d.ts +2 -0
- package/dist/src/intelligenceProtocol/activationSchemas.js +1 -0
- package/dist/src/intelligenceProtocol/index.d.ts +9 -2
- package/dist/src/intelligenceProtocol/index.js +5 -1
- package/dist/src/intelligenceProtocol/operatorRegistry.d.ts +567 -0
- package/dist/src/intelligenceProtocol/operatorRegistry.js +477 -0
- package/dist/src/intelligenceProtocol/patternV2Schemas.d.ts +188 -0
- package/dist/src/intelligenceProtocol/patternV2Schemas.js +801 -0
- package/dist/src/intelligenceProtocol/schemas.d.ts +4 -0
- package/dist/src/intelligenceProtocol/schemas.js +2 -0
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +177 -4
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +37 -4
- package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +256 -0
- package/dist/src/intelligenceProtocol/triggerSchemas.js +161 -2
- package/dist/src/mcpEvents/consumer.d.ts +1 -0
- package/dist/src/mcpEvents/consumer.js +1 -0
- package/dist/src/patternV2/evaluator.d.ts +43 -0
- package/dist/src/patternV2/evaluator.js +927 -0
- package/dist/src/patternV2/legacyCompiler.d.ts +3 -0
- package/dist/src/patternV2/legacyCompiler.js +184 -0
- package/package.json +5 -2
- package/scripts/host-integration.d.mts +7 -0
- package/scripts/host-integration.mjs +9 -0
- package/scripts/lib/activation-hydrator.mjs +1 -0
- package/scripts/lib/composite-event-consumer.mjs +2 -0
- package/scripts/lib/derived-event-coordinator.mjs +75 -1
- package/scripts/lib/mcp-events-client.mjs +62 -24
- package/scripts/lib/mcp-events-compatibility.mjs +54 -0
- package/scripts/lib/trigger-control-plane.mjs +154 -0
- package/scripts/lib/trigger-inspector.mjs +15 -0
- package/scripts/lib/trigger-planner.mjs +190 -12
- package/scripts/mcp-stdio-server.mjs +38 -53
- package/server.json +4 -4
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { CompositeTriggerDefinition } from '../intelligenceProtocol/triggerSchemas.js';
|
|
2
|
+
import { type PatternAstV2Definition } from '../intelligenceProtocol/patternV2Schemas.js';
|
|
3
|
+
export declare function compileLegacyTriggerToPatternV2(definition: CompositeTriggerDefinition): PatternAstV2Definition;
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { PatternAstV2DefinitionSchema, } from '../intelligenceProtocol/patternV2Schemas.js';
|
|
2
|
+
function eventNode(ref) {
|
|
3
|
+
return { kind: 'event', ref };
|
|
4
|
+
}
|
|
5
|
+
function expressionNode(definition) {
|
|
6
|
+
const expression = definition.expression;
|
|
7
|
+
if (expression.kind === 'allOf') {
|
|
8
|
+
return {
|
|
9
|
+
kind: 'allOf',
|
|
10
|
+
children: expression.refs.map(eventNode),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
if (expression.kind === 'anyOf') {
|
|
14
|
+
return {
|
|
15
|
+
kind: 'anyOf',
|
|
16
|
+
children: expression.refs.map(eventNode),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
if (expression.kind === 'sequence') {
|
|
20
|
+
return {
|
|
21
|
+
kind: 'sequence',
|
|
22
|
+
children: expression.refs.map(eventNode),
|
|
23
|
+
contiguity: 'followedBy',
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
kind: 'repeat',
|
|
28
|
+
child: eventNode(expression.ref),
|
|
29
|
+
min: expression.atLeast,
|
|
30
|
+
mode: 'greedy',
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function wrapTemporal(root, condition) {
|
|
34
|
+
if (condition.kind === 'calendar') {
|
|
35
|
+
return {
|
|
36
|
+
kind: 'calendar',
|
|
37
|
+
child: root,
|
|
38
|
+
ref: condition.ref,
|
|
39
|
+
timezone: condition.timezone,
|
|
40
|
+
...(condition.before ? { before: condition.before } : {}),
|
|
41
|
+
...(condition.after ? { after: condition.after } : {}),
|
|
42
|
+
...(condition.weekdays ? { weekdays: condition.weekdays } : {}),
|
|
43
|
+
...(condition.dates ? { dates: condition.dates } : {}),
|
|
44
|
+
...(condition.dateRange ? { dateRange: condition.dateRange } : {}),
|
|
45
|
+
...(condition.dayOfMonth ? { dayOfMonth: condition.dayOfMonth } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (condition.kind === 'absence') {
|
|
49
|
+
return {
|
|
50
|
+
kind: 'absence',
|
|
51
|
+
id: condition.id,
|
|
52
|
+
child: root,
|
|
53
|
+
ref: condition.ref,
|
|
54
|
+
afterRef: condition.afterRef,
|
|
55
|
+
...(condition.forMs ? { forMs: condition.forMs } : {}),
|
|
56
|
+
...(condition.untilLocalTime
|
|
57
|
+
? { untilLocalTime: condition.untilLocalTime }
|
|
58
|
+
: {}),
|
|
59
|
+
...(condition.timezone ? { timezone: condition.timezone } : {}),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
if (condition.kind === 'not' || condition.kind === 'unless') {
|
|
63
|
+
return {
|
|
64
|
+
kind: 'notPresent',
|
|
65
|
+
child: root,
|
|
66
|
+
ref: condition.ref,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (condition.kind === 'after') {
|
|
70
|
+
return {
|
|
71
|
+
kind: 'after',
|
|
72
|
+
child: root,
|
|
73
|
+
ref: condition.ref,
|
|
74
|
+
afterRef: condition.afterRef,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
if (condition.kind === 'until') {
|
|
78
|
+
return {
|
|
79
|
+
kind: 'until',
|
|
80
|
+
child: root,
|
|
81
|
+
ref: condition.ref,
|
|
82
|
+
beforeRef: condition.beforeRef,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (condition.kind === 'debounce') {
|
|
86
|
+
return {
|
|
87
|
+
kind: 'debounce',
|
|
88
|
+
id: condition.id,
|
|
89
|
+
child: root,
|
|
90
|
+
ref: condition.ref,
|
|
91
|
+
forMs: condition.forMs,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (condition.kind === 'threshold') {
|
|
95
|
+
return {
|
|
96
|
+
kind: 'threshold',
|
|
97
|
+
child: root,
|
|
98
|
+
ref: condition.ref,
|
|
99
|
+
atLeast: condition.atLeast,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (condition.kind === 'rate') {
|
|
103
|
+
return {
|
|
104
|
+
kind: 'rate',
|
|
105
|
+
child: root,
|
|
106
|
+
ref: condition.ref,
|
|
107
|
+
atLeast: condition.atLeast,
|
|
108
|
+
perMs: condition.perMs,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
kind: 'distinct',
|
|
113
|
+
child: root,
|
|
114
|
+
ref: condition.ref,
|
|
115
|
+
path: condition.path,
|
|
116
|
+
atLeast: condition.atLeast,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
export function compileLegacyTriggerToPatternV2(definition) {
|
|
120
|
+
let root = expressionNode(definition);
|
|
121
|
+
const deterministic = definition.correlation?.deterministic;
|
|
122
|
+
if (deterministic?.fields?.length) {
|
|
123
|
+
const [anchorField, ...otherFields] = deterministic.fields;
|
|
124
|
+
if (!anchorField) {
|
|
125
|
+
throw new Error('Deterministic correlation requires at least one field');
|
|
126
|
+
}
|
|
127
|
+
for (const field of otherFields) {
|
|
128
|
+
root = {
|
|
129
|
+
kind: 'compare',
|
|
130
|
+
child: root,
|
|
131
|
+
left: {
|
|
132
|
+
kind: 'field',
|
|
133
|
+
ref: anchorField.ref,
|
|
134
|
+
path: anchorField.path,
|
|
135
|
+
select: 'last',
|
|
136
|
+
},
|
|
137
|
+
op: 'eq',
|
|
138
|
+
right: {
|
|
139
|
+
kind: 'field',
|
|
140
|
+
ref: field.ref,
|
|
141
|
+
path: field.path,
|
|
142
|
+
select: 'last',
|
|
143
|
+
},
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for (const condition of definition.temporal ?? []) {
|
|
148
|
+
root = wrapTemporal(root, condition);
|
|
149
|
+
}
|
|
150
|
+
if (definition.correlation?.semantic) {
|
|
151
|
+
const semantic = definition.correlation.semantic;
|
|
152
|
+
root = {
|
|
153
|
+
kind: 'semantic',
|
|
154
|
+
id: 'legacy-semantic-correlation',
|
|
155
|
+
child: root,
|
|
156
|
+
refs: [...new Set(semantic.input
|
|
157
|
+
.map((path) => path.split('.')[0])
|
|
158
|
+
.filter(Boolean))],
|
|
159
|
+
instruction: semantic.instruction,
|
|
160
|
+
input: semantic.input,
|
|
161
|
+
matchThreshold: semantic.matchThreshold,
|
|
162
|
+
rejectThreshold: semantic.rejectThreshold,
|
|
163
|
+
uncertain: semantic.uncertain,
|
|
164
|
+
execution: {
|
|
165
|
+
cache: true,
|
|
166
|
+
timeoutMs: 5000,
|
|
167
|
+
},
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return PatternAstV2DefinitionSchema.parse({
|
|
171
|
+
version: '2',
|
|
172
|
+
root,
|
|
173
|
+
partitionBy: [],
|
|
174
|
+
selection: {
|
|
175
|
+
overlap: 'disallow',
|
|
176
|
+
afterMatch: 'skipPastLast',
|
|
177
|
+
maxMatchesPerEvent: 10,
|
|
178
|
+
},
|
|
179
|
+
execution: {
|
|
180
|
+
maxCandidates: 512,
|
|
181
|
+
maxSemanticEvaluations: 16,
|
|
182
|
+
},
|
|
183
|
+
});
|
|
184
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mcp-event-intelligence",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "MCP-native complex event processing for durable agent workflows: event-time patterns, semantic predicates, derived events, and targeted wakeups.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/src/intelligenceProtocol/index.js",
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
"model-context-protocol",
|
|
62
62
|
"ai-agents",
|
|
63
63
|
"event-driven",
|
|
64
|
+
"complex-event-processing",
|
|
65
|
+
"cep",
|
|
66
|
+
"event-time",
|
|
64
67
|
"temporal",
|
|
65
68
|
"durable-execution",
|
|
66
69
|
"triggers",
|
|
@@ -56,6 +56,8 @@ export interface HostMcpConnectionOptions {
|
|
|
56
56
|
maxPollBatches?: number;
|
|
57
57
|
/** Prefer one advertised delivery mode when the host can provide it. */
|
|
58
58
|
preferredDelivery?: 'poll' | 'push' | 'webhook';
|
|
59
|
+
/** Versioned MCP Events wire profile. Defaults to auto/current experimental snapshot. */
|
|
60
|
+
compatibilityProfile?: 'auto' | 'experimental-extension-2026-09-25' | string;
|
|
59
61
|
/** Host-owned adapter for events/stream delivery. */
|
|
60
62
|
openEventStream?: HostMcpEventDeliveryOpener;
|
|
61
63
|
/** Host-owned adapter for webhook subscription/receiver delivery. */
|
|
@@ -113,6 +115,10 @@ export interface EventIntelligenceScopedHost {
|
|
|
113
115
|
readonly triggerPlanner: any;
|
|
114
116
|
readonly activationHydrator: any;
|
|
115
117
|
readonly eventSources: any[];
|
|
118
|
+
describeTriggerLanguage(input?: {
|
|
119
|
+
category?: 'predicates' | 'composition' | 'temporal' | 'pattern' | 'windows' | 'aggregates' | 'state' | 'selection' | 'semantic' | 'execution' | 'correlation' | 'timing' | 'lifecycle';
|
|
120
|
+
operator?: string;
|
|
121
|
+
}): Record<string, unknown>;
|
|
116
122
|
planTrigger(input: TriggerPlanInput): Promise<{
|
|
117
123
|
planVersion: '1';
|
|
118
124
|
definition: any;
|
|
@@ -154,6 +160,7 @@ export function createHostMcpEventsConnection(
|
|
|
154
160
|
maxEvents: number;
|
|
155
161
|
maxPollBatches: number;
|
|
156
162
|
preferredDelivery: 'poll' | 'push' | 'webhook' | null;
|
|
163
|
+
compatibilityProfile: string;
|
|
157
164
|
openEventStream: HostMcpEventDeliveryOpener | null;
|
|
158
165
|
createWebhookSubscription: HostMcpEventDeliveryOpener | null;
|
|
159
166
|
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describeTriggerLanguage,
|
|
3
|
+
} from '../dist/src/intelligenceProtocol/index.js';
|
|
1
4
|
import {
|
|
2
5
|
createLocalEventIntelligenceRuntime,
|
|
3
6
|
} from './lib/local-event-intelligence-runtime.mjs';
|
|
@@ -79,6 +82,9 @@ function scopedHostView(runtime, context) {
|
|
|
79
82
|
planTrigger(input) {
|
|
80
83
|
return context.triggerPlanner.plan(input);
|
|
81
84
|
},
|
|
85
|
+
describeTriggerLanguage(input = {}) {
|
|
86
|
+
return describeTriggerLanguage(input);
|
|
87
|
+
},
|
|
82
88
|
hydrateWake(wakeId) {
|
|
83
89
|
return context.activationHydrator.hydrateWake(wakeId);
|
|
84
90
|
},
|
|
@@ -240,6 +246,9 @@ export async function createEventIntelligenceHost({
|
|
|
240
246
|
planTrigger(input) {
|
|
241
247
|
return runtime.triggerPlanner.plan(input);
|
|
242
248
|
},
|
|
249
|
+
describeTriggerLanguage(input = {}) {
|
|
250
|
+
return describeTriggerLanguage(input);
|
|
251
|
+
},
|
|
243
252
|
hydrateWake(wakeId) {
|
|
244
253
|
return runtime.activationHydrator.hydrateWake(wakeId);
|
|
245
254
|
},
|
|
@@ -100,6 +100,7 @@ export class ActivationHydrator {
|
|
|
100
100
|
eventName: event.eventName,
|
|
101
101
|
traceId: event.traceId,
|
|
102
102
|
occurredAt: event.occurredAt,
|
|
103
|
+
...(event.receivedAt ? { receivedAt: event.receivedAt } : {}),
|
|
103
104
|
payloadHash: event.payloadHash ?? null,
|
|
104
105
|
...(includeData ? { data: event.data } : {}),
|
|
105
106
|
}));
|
|
@@ -35,6 +35,7 @@ export class CompositeEventConsumer {
|
|
|
35
35
|
provider,
|
|
36
36
|
traceId,
|
|
37
37
|
subscriptionArguments,
|
|
38
|
+
receivedAt,
|
|
38
39
|
}) {
|
|
39
40
|
const correlatable = await mcpOccurrenceToCorrelatableEvent(
|
|
40
41
|
event,
|
|
@@ -43,6 +44,7 @@ export class CompositeEventConsumer {
|
|
|
43
44
|
...(provider ? { provider } : {}),
|
|
44
45
|
...(serverId ? { serverId } : {}),
|
|
45
46
|
subscriptionArguments: subscriptionArguments ?? {},
|
|
47
|
+
...(receivedAt ? { receivedAt } : {}),
|
|
46
48
|
},
|
|
47
49
|
);
|
|
48
50
|
|
|
@@ -2,6 +2,12 @@ import {
|
|
|
2
2
|
parseDerivedEventRecord,
|
|
3
3
|
sha256Hex,
|
|
4
4
|
} from '../../dist/src/intelligenceProtocol/index.js';
|
|
5
|
+
import {
|
|
6
|
+
evaluateTemporalConditions,
|
|
7
|
+
} from '../../dist/src/composite/temporal.js';
|
|
8
|
+
import {
|
|
9
|
+
evaluatePatternMeasure,
|
|
10
|
+
} from '../../dist/src/patternV2/evaluator.js';
|
|
5
11
|
|
|
6
12
|
export const DERIVED_EVENT_SERVER_ID = 'event-intelligence:derived';
|
|
7
13
|
export const DERIVED_EVENT_CONNECTION_ID = 'event-intelligence:derived';
|
|
@@ -26,6 +32,7 @@ function evidenceRef(source) {
|
|
|
26
32
|
sourceEventId: source.sourceEventId,
|
|
27
33
|
traceId: source.traceId,
|
|
28
34
|
occurredAt: source.occurredAt,
|
|
35
|
+
...(source.receivedAt ? { receivedAt: source.receivedAt } : {}),
|
|
29
36
|
payloadHash: source.payloadHash ?? null,
|
|
30
37
|
contractVersion:
|
|
31
38
|
source.data?._derived?.contractVersion ?? null,
|
|
@@ -60,6 +67,51 @@ function latestForClause(match, clauseId) {
|
|
|
60
67
|
)[0] ?? null;
|
|
61
68
|
}
|
|
62
69
|
|
|
70
|
+
function patternCandidateForMatch(match) {
|
|
71
|
+
const bindings = {};
|
|
72
|
+
for (const event of match.sourceEvents) {
|
|
73
|
+
bindings[event.clauseId] ??= [];
|
|
74
|
+
bindings[event.clauseId].push(event);
|
|
75
|
+
}
|
|
76
|
+
for (const events of Object.values(bindings)) {
|
|
77
|
+
events.sort((a, b) =>
|
|
78
|
+
Date.parse(a.occurredAt) - Date.parse(b.occurredAt) ||
|
|
79
|
+
a.sourceEventId.localeCompare(b.sourceEventId)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
const events = [...match.sourceEvents].sort((a, b) =>
|
|
83
|
+
Date.parse(a.occurredAt) - Date.parse(b.occurredAt) ||
|
|
84
|
+
a.sourceEventId.localeCompare(b.sourceEventId)
|
|
85
|
+
);
|
|
86
|
+
return {
|
|
87
|
+
bindings,
|
|
88
|
+
events,
|
|
89
|
+
startIndex: events.length ? 0 : -1,
|
|
90
|
+
endIndex: events.length ? events.length - 1 : -1,
|
|
91
|
+
semanticDecisions: [],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function derivedOccurredAt(match, definition) {
|
|
96
|
+
const candidates = match.sourceEvents
|
|
97
|
+
.map((event) => Date.parse(event.occurredAt))
|
|
98
|
+
.filter(Number.isFinite);
|
|
99
|
+
|
|
100
|
+
const temporal = evaluateTemporalConditions(
|
|
101
|
+
definition,
|
|
102
|
+
match,
|
|
103
|
+
new Date(match.updatedAt),
|
|
104
|
+
);
|
|
105
|
+
for (const state of temporal.conditionStates) {
|
|
106
|
+
if (state.status !== 'satisfied' || !state.dueAt) continue;
|
|
107
|
+
const dueAt = Date.parse(state.dueAt);
|
|
108
|
+
if (Number.isFinite(dueAt)) candidates.push(dueAt);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (!candidates.length) return match.updatedAt;
|
|
112
|
+
return new Date(Math.max(...candidates)).toISOString();
|
|
113
|
+
}
|
|
114
|
+
|
|
63
115
|
export class DerivedEventCoordinator {
|
|
64
116
|
constructor({
|
|
65
117
|
store,
|
|
@@ -147,6 +199,26 @@ export class DerivedEventCoordinator {
|
|
|
147
199
|
}
|
|
148
200
|
}
|
|
149
201
|
|
|
202
|
+
const measured = {};
|
|
203
|
+
if (spec.measures?.length) {
|
|
204
|
+
const candidate = patternCandidateForMatch(match);
|
|
205
|
+
for (const measure of spec.measures) {
|
|
206
|
+
const value = evaluatePatternMeasure(candidate, measure);
|
|
207
|
+
if (
|
|
208
|
+
value === undefined ||
|
|
209
|
+
(
|
|
210
|
+
value !== null &&
|
|
211
|
+
!['string', 'number', 'boolean'].includes(typeof value)
|
|
212
|
+
)
|
|
213
|
+
) {
|
|
214
|
+
throw new Error(
|
|
215
|
+
`Derived event measure must resolve to scalar or null: ${measure.key}`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
measured[measure.key] = value;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
150
222
|
const contract = this.store.getDerivedContract(
|
|
151
223
|
spec.name,
|
|
152
224
|
spec.contractVersion,
|
|
@@ -162,6 +234,7 @@ export class DerivedEventCoordinator {
|
|
|
162
234
|
const data = {
|
|
163
235
|
...(spec.constants ?? {}),
|
|
164
236
|
...projected,
|
|
237
|
+
...measured,
|
|
165
238
|
_derived: {
|
|
166
239
|
triggerId: definition.triggerId,
|
|
167
240
|
triggerVersion: definition.version,
|
|
@@ -176,7 +249,8 @@ export class DerivedEventCoordinator {
|
|
|
176
249
|
traceId: `derived:${eventId}`,
|
|
177
250
|
sourceEventId: eventId,
|
|
178
251
|
name: spec.name,
|
|
179
|
-
occurredAt: match
|
|
252
|
+
occurredAt: derivedOccurredAt(match, definition),
|
|
253
|
+
receivedAt: this.now().toISOString(),
|
|
180
254
|
provider: 'event-intelligence',
|
|
181
255
|
serverId: DERIVED_EVENT_SERVER_ID,
|
|
182
256
|
payloadHash,
|
|
@@ -8,10 +8,14 @@ import {
|
|
|
8
8
|
normalizeEventScopeId,
|
|
9
9
|
} from './persistent-event-store.mjs';
|
|
10
10
|
import { assertJsonSchemaValue } from './json-schema.mjs';
|
|
11
|
+
import {
|
|
12
|
+
MCP_EVENTS_EXTENSION_ID,
|
|
13
|
+
getMcpEventsCompatibilityProfile,
|
|
14
|
+
resolveMcpEventsCompatibilityProfile,
|
|
15
|
+
} from './mcp-events-compatibility.mjs';
|
|
11
16
|
|
|
12
17
|
export { assertJsonSchemaValue } from './json-schema.mjs';
|
|
13
|
-
|
|
14
|
-
export const MCP_EVENTS_EXTENSION_ID = 'io.modelcontextprotocol/events';
|
|
18
|
+
export { MCP_EVENTS_EXTENSION_ID } from './mcp-events-compatibility.mjs';
|
|
15
19
|
const UnknownResultSchema = z.unknown();
|
|
16
20
|
|
|
17
21
|
function assertObject(value, message) {
|
|
@@ -116,6 +120,10 @@ function normalizeConnection(connection) {
|
|
|
116
120
|
'maxPollBatches',
|
|
117
121
|
),
|
|
118
122
|
preferredDelivery,
|
|
123
|
+
compatibilityProfile:
|
|
124
|
+
connection.compatibilityProfile === undefined
|
|
125
|
+
? 'auto'
|
|
126
|
+
: String(connection.compatibilityProfile),
|
|
119
127
|
openEventStream:
|
|
120
128
|
typeof connection.openEventStream === 'function'
|
|
121
129
|
? connection.openEventStream
|
|
@@ -148,6 +156,7 @@ export function createHostMcpEventsConnection({
|
|
|
148
156
|
maxPollBatches = 4,
|
|
149
157
|
scopeId = DEFAULT_EVENT_SCOPE_ID,
|
|
150
158
|
preferredDelivery,
|
|
159
|
+
compatibilityProfile = 'auto',
|
|
151
160
|
openEventStream,
|
|
152
161
|
createWebhookSubscription,
|
|
153
162
|
} = {}) {
|
|
@@ -185,12 +194,13 @@ export function createHostMcpEventsConnection({
|
|
|
185
194
|
maxPollBatches,
|
|
186
195
|
scopeId,
|
|
187
196
|
preferredDelivery,
|
|
197
|
+
compatibilityProfile,
|
|
188
198
|
openEventStream,
|
|
189
199
|
createWebhookSubscription,
|
|
190
200
|
});
|
|
191
201
|
}
|
|
192
202
|
|
|
193
|
-
function sourceDescriptor(raw) {
|
|
203
|
+
function sourceDescriptor(raw, profile) {
|
|
194
204
|
const descriptor = assertObject(raw, 'Invalid MCP event descriptor');
|
|
195
205
|
const name = String(descriptor.name || '').trim();
|
|
196
206
|
if (!name) throw new Error('MCP event descriptor requires name');
|
|
@@ -199,7 +209,7 @@ function sourceDescriptor(raw) {
|
|
|
199
209
|
: [];
|
|
200
210
|
if (
|
|
201
211
|
delivery.length === 0 ||
|
|
202
|
-
delivery.some((mode) => !
|
|
212
|
+
delivery.some((mode) => !profile.deliveryModes.includes(mode))
|
|
203
213
|
) {
|
|
204
214
|
throw new Error(`MCP event descriptor ${name} has invalid delivery modes`);
|
|
205
215
|
}
|
|
@@ -284,6 +294,7 @@ export class McpEventsClientManager {
|
|
|
284
294
|
this.connections = new Map();
|
|
285
295
|
this.now = now;
|
|
286
296
|
this.descriptors = new Map();
|
|
297
|
+
this.profiles = new Map();
|
|
287
298
|
this.timers = new Map();
|
|
288
299
|
this.lastErrors = new Map();
|
|
289
300
|
this.scopeContexts = new Map();
|
|
@@ -364,6 +375,7 @@ export class McpEventsClientManager {
|
|
|
364
375
|
if (timer) clearTimeout(timer);
|
|
365
376
|
this.timers.delete(connectionId);
|
|
366
377
|
this.descriptors.delete(connectionId);
|
|
378
|
+
this.profiles.delete(connectionId);
|
|
367
379
|
this.lastErrors.delete(connectionId);
|
|
368
380
|
this.pollInFlight.delete(connectionId);
|
|
369
381
|
|
|
@@ -426,20 +438,16 @@ export class McpEventsClientManager {
|
|
|
426
438
|
|
|
427
439
|
const context = await this.scopeContext(connection);
|
|
428
440
|
const capabilities = (await connection.getCapabilities()) ?? {};
|
|
429
|
-
const
|
|
430
|
-
capabilities
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
extensions,
|
|
438
|
-
MCP_EVENTS_EXTENSION_ID,
|
|
439
|
-
)
|
|
440
|
-
) {
|
|
441
|
+
const profile = resolveMcpEventsCompatibilityProfile({
|
|
442
|
+
capabilities,
|
|
443
|
+
requested: connection.compatibilityProfile,
|
|
444
|
+
});
|
|
445
|
+
if (!profile) {
|
|
446
|
+
const expected = getMcpEventsCompatibilityProfile(
|
|
447
|
+
connection.compatibilityProfile,
|
|
448
|
+
);
|
|
441
449
|
const error = new Error(
|
|
442
|
-
`MCP server ${connectionId} does not advertise ${
|
|
450
|
+
`MCP server ${connectionId} does not advertise the ${expected.id} Events profile`,
|
|
443
451
|
);
|
|
444
452
|
error.code = 'MCP_EVENTS_CAPABILITY_UNAVAILABLE';
|
|
445
453
|
throw error;
|
|
@@ -451,7 +459,7 @@ export class McpEventsClientManager {
|
|
|
451
459
|
do {
|
|
452
460
|
const listed = await this.rpc(
|
|
453
461
|
connection,
|
|
454
|
-
|
|
462
|
+
profile.listMethod,
|
|
455
463
|
cursor ? { cursor } : {},
|
|
456
464
|
);
|
|
457
465
|
if (!Array.isArray(listed?.events)) {
|
|
@@ -460,7 +468,7 @@ export class McpEventsClientManager {
|
|
|
460
468
|
);
|
|
461
469
|
}
|
|
462
470
|
for (const raw of listed.events) {
|
|
463
|
-
const descriptor = sourceDescriptor(raw);
|
|
471
|
+
const descriptor = sourceDescriptor(raw, profile);
|
|
464
472
|
if (names.has(descriptor.name)) {
|
|
465
473
|
throw new Error(
|
|
466
474
|
`MCP server ${connectionId} returned duplicate event descriptor ${descriptor.name}`,
|
|
@@ -492,7 +500,9 @@ export class McpEventsClientManager {
|
|
|
492
500
|
JSON.stringify(existing.inputSchema || {}) ===
|
|
493
501
|
JSON.stringify(normalized.inputSchema) &&
|
|
494
502
|
JSON.stringify(existing.payloadSchema || {}) ===
|
|
495
|
-
JSON.stringify(normalized.payloadSchema)
|
|
503
|
+
JSON.stringify(normalized.payloadSchema) &&
|
|
504
|
+
existing.metadata?.compatibilityProfile === profile.id &&
|
|
505
|
+
existing.metadata?.extensionId === profile.extensionId;
|
|
496
506
|
|
|
497
507
|
if (!same) {
|
|
498
508
|
const register = context.triggerControl?.registerEventSource
|
|
@@ -514,7 +524,8 @@ export class McpEventsClientManager {
|
|
|
514
524
|
metadata: {
|
|
515
525
|
transport: 'host-managed-mcp',
|
|
516
526
|
hostManaged: true,
|
|
517
|
-
extensionId:
|
|
527
|
+
extensionId: profile.extensionId,
|
|
528
|
+
compatibilityProfile: profile.id,
|
|
518
529
|
...(normalized._meta ? { descriptorMeta: normalized._meta } : {}),
|
|
519
530
|
},
|
|
520
531
|
},
|
|
@@ -527,6 +538,7 @@ export class McpEventsClientManager {
|
|
|
527
538
|
}
|
|
528
539
|
|
|
529
540
|
this.descriptors.set(connection.connectionId, descriptors);
|
|
541
|
+
this.profiles.set(connection.connectionId, profile);
|
|
530
542
|
this.lastErrors.delete(connection.connectionId);
|
|
531
543
|
return descriptors;
|
|
532
544
|
}
|
|
@@ -596,12 +608,18 @@ export class McpEventsClientManager {
|
|
|
596
608
|
clause.event,
|
|
597
609
|
args,
|
|
598
610
|
);
|
|
599
|
-
|
|
611
|
+
const consumerRef =
|
|
612
|
+
`${definition.triggerId}@${definition.version}:${clause.id}`;
|
|
613
|
+
const existing = desired.get(subscriptionId);
|
|
614
|
+
if (existing) {
|
|
615
|
+
existing.consumerRefs.push(consumerRef);
|
|
616
|
+
} else {
|
|
600
617
|
desired.set(subscriptionId, {
|
|
601
618
|
subscriptionId,
|
|
602
619
|
eventName: clause.event,
|
|
603
620
|
arguments: args,
|
|
604
621
|
descriptor,
|
|
622
|
+
consumerRefs: [consumerRef],
|
|
605
623
|
});
|
|
606
624
|
}
|
|
607
625
|
}
|
|
@@ -620,6 +638,7 @@ export class McpEventsClientManager {
|
|
|
620
638
|
}
|
|
621
639
|
assertJsonSchemaValue(descriptor.payloadSchema, occurrence.data);
|
|
622
640
|
|
|
641
|
+
const receivedAt = this.now().toISOString();
|
|
623
642
|
const receipt = await context.store.appendMcpOccurrence(
|
|
624
643
|
connection.serverId,
|
|
625
644
|
occurrence,
|
|
@@ -634,6 +653,7 @@ export class McpEventsClientManager {
|
|
|
634
653
|
traceId:
|
|
635
654
|
`mcp:${connection.serverId}:${subscription.subscriptionId}:${occurrence.eventId}`,
|
|
636
655
|
subscriptionArguments: subscription.arguments,
|
|
656
|
+
receivedAt,
|
|
637
657
|
});
|
|
638
658
|
return { accepted: true, occurrence };
|
|
639
659
|
}
|
|
@@ -683,6 +703,7 @@ export class McpEventsClientManager {
|
|
|
683
703
|
delivery: 'poll',
|
|
684
704
|
status: 'scheduled',
|
|
685
705
|
accepted: 0,
|
|
706
|
+
consumerCount: subscription.consumerRefs.length,
|
|
686
707
|
cursor: current.cursor,
|
|
687
708
|
nextPollAt: current.nextPollAt,
|
|
688
709
|
};
|
|
@@ -697,7 +718,10 @@ export class McpEventsClientManager {
|
|
|
697
718
|
let nextPollMs = connection.pollIntervalMs;
|
|
698
719
|
|
|
699
720
|
do {
|
|
700
|
-
const
|
|
721
|
+
const profile =
|
|
722
|
+
this.profiles.get(connection.connectionId) ??
|
|
723
|
+
getMcpEventsCompatibilityProfile(connection.compatibilityProfile);
|
|
724
|
+
const result = await this.rpc(connection, profile.pollMethod, {
|
|
701
725
|
name: subscription.eventName,
|
|
702
726
|
arguments: subscription.arguments,
|
|
703
727
|
cursor,
|
|
@@ -757,6 +781,7 @@ export class McpEventsClientManager {
|
|
|
757
781
|
delivery: 'poll',
|
|
758
782
|
status: 'ok',
|
|
759
783
|
accepted,
|
|
784
|
+
consumerCount: subscription.consumerRefs.length,
|
|
760
785
|
cursor,
|
|
761
786
|
truncated,
|
|
762
787
|
hasMore,
|
|
@@ -780,6 +805,7 @@ export class McpEventsClientManager {
|
|
|
780
805
|
delivery,
|
|
781
806
|
status: 'active',
|
|
782
807
|
accepted: existing.accepted,
|
|
808
|
+
consumerCount: subscription.consumerRefs.length,
|
|
783
809
|
};
|
|
784
810
|
}
|
|
785
811
|
if (existing) await this.closeSession(subscription.subscriptionId);
|
|
@@ -922,6 +948,7 @@ export class McpEventsClientManager {
|
|
|
922
948
|
delivery,
|
|
923
949
|
status: 'active',
|
|
924
950
|
accepted,
|
|
951
|
+
consumerCount: subscription.consumerRefs.length,
|
|
925
952
|
};
|
|
926
953
|
}
|
|
927
954
|
|
|
@@ -981,6 +1008,7 @@ export class McpEventsClientManager {
|
|
|
981
1008
|
status: 'delivery_not_supported',
|
|
982
1009
|
delivery: null,
|
|
983
1010
|
accepted: 0,
|
|
1011
|
+
consumerCount: subscription.consumerRefs.length,
|
|
984
1012
|
});
|
|
985
1013
|
continue;
|
|
986
1014
|
}
|
|
@@ -1115,12 +1143,20 @@ export class McpEventsClientManager {
|
|
|
1115
1143
|
);
|
|
1116
1144
|
const store = context?.store ?? this.store;
|
|
1117
1145
|
const states = store.listMcpClientStates(connection.connectionId);
|
|
1146
|
+
const desired = context
|
|
1147
|
+
? this.desiredSubscriptions(connection, context)
|
|
1148
|
+
: new Map();
|
|
1118
1149
|
return {
|
|
1119
1150
|
connectionId: connection.connectionId,
|
|
1120
1151
|
scopeId: connection.scopeId,
|
|
1121
1152
|
serverId: connection.serverId,
|
|
1122
1153
|
hostManaged: true,
|
|
1123
|
-
extensionId:
|
|
1154
|
+
extensionId:
|
|
1155
|
+
this.profiles.get(connection.connectionId)?.extensionId ??
|
|
1156
|
+
MCP_EVENTS_EXTENSION_ID,
|
|
1157
|
+
compatibilityProfile:
|
|
1158
|
+
this.profiles.get(connection.connectionId)?.id ??
|
|
1159
|
+
connection.compatibilityProfile,
|
|
1124
1160
|
events: (this.descriptors.get(connection.connectionId) || []).map(
|
|
1125
1161
|
(event) => ({
|
|
1126
1162
|
name: event.name,
|
|
@@ -1130,6 +1166,8 @@ export class McpEventsClientManager {
|
|
|
1130
1166
|
.map((state) => ({
|
|
1131
1167
|
subscriptionId: state.subscriptionId,
|
|
1132
1168
|
arguments: state.arguments ?? {},
|
|
1169
|
+
consumerCount:
|
|
1170
|
+
desired.get(state.subscriptionId)?.consumerRefs.length ?? 0,
|
|
1133
1171
|
delivery: state.deliveryMode,
|
|
1134
1172
|
cursor: state.cursor,
|
|
1135
1173
|
nextPollAt: state.nextPollAt ?? null,
|