mcp-event-intelligence 0.4.0 → 0.6.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 +100 -0
- package/README.md +107 -26
- package/SECURITY.md +4 -4
- package/bin/mcp-event-intelligence.mjs +3 -3
- package/dist/src/composite/engine.d.ts +14 -4
- package/dist/src/composite/engine.js +533 -208
- package/dist/src/composite/matching.d.ts +1 -5
- package/dist/src/composite/matching.js +45 -54
- package/dist/src/composite/runtimeSupport.js +10 -6
- package/dist/src/composite/store.d.ts +38 -4
- package/dist/src/composite/temporal.d.ts +0 -17
- package/dist/src/composite/temporal.js +0 -266
- package/dist/src/intelligenceProtocol/activationSchemas.d.ts +38 -3
- package/dist/src/intelligenceProtocol/activationSchemas.js +5 -3
- package/dist/src/intelligenceProtocol/index.d.ts +12 -9
- package/dist/src/intelligenceProtocol/index.js +6 -5
- package/dist/src/intelligenceProtocol/lifecycle.js +2 -10
- package/dist/src/intelligenceProtocol/operatorRegistry.d.ts +300 -0
- package/dist/src/intelligenceProtocol/operatorRegistry.js +338 -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 -189
- package/dist/src/intelligenceProtocol/schemas.js +3 -97
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +114 -38
- package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +27 -18
- package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +268 -213
- package/dist/src/intelligenceProtocol/triggerSchemas.js +155 -169
- package/dist/src/intelligenceProtocol/version.d.ts +2 -2
- package/dist/src/intelligenceProtocol/version.js +2 -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 +51 -0
- package/dist/src/patternV2/evaluator.js +986 -0
- package/dist/src/semantic/types.d.ts +6 -0
- package/dist/src/semantic/typesafeJevEvaluator.d.ts +1 -0
- package/dist/src/semantic/typesafeJevEvaluator.js +6 -0
- package/package.json +9 -3
- package/scripts/host-integration.d.mts +54 -4
- package/scripts/host-integration.mjs +25 -4
- package/scripts/lib/activation-hydrator.mjs +13 -12
- package/scripts/lib/composite-event-consumer.mjs +10 -28
- package/scripts/lib/composite-wake-coordinator.mjs +19 -19
- package/scripts/lib/derived-contract-registry.mjs +4 -4
- package/scripts/lib/derived-event-coordinator.mjs +69 -6
- package/scripts/lib/local-event-intelligence-runtime.mjs +35 -3
- package/scripts/lib/mcp-events-client.mjs +97 -55
- package/scripts/lib/mcp-events-compatibility.mjs +54 -0
- package/scripts/lib/persistent-event-store.mjs +228 -61
- package/scripts/lib/store-capabilities.mjs +88 -0
- package/scripts/lib/store-conformance.mjs +127 -0
- package/scripts/lib/temporal-deadline-scheduler.mjs +3 -3
- package/scripts/lib/trigger-control-plane.mjs +202 -53
- package/scripts/lib/trigger-inspector.mjs +50 -147
- package/scripts/lib/trigger-planner.mjs +141 -52
- package/scripts/lib/wake-retry-scheduler.mjs +2 -19
- package/scripts/mcp-stdio-server.mjs +46 -61
- package/scripts/service.mjs +19 -138
- package/server.json +4 -4
- package/dist/src/intelligenceProtocol/invariants.d.ts +0 -3
- package/dist/src/intelligenceProtocol/invariants.js +0 -36
- package/dist/src/intelligenceProtocol/mapper.d.ts +0 -14
- package/dist/src/intelligenceProtocol/mapper.js +0 -28
- package/scripts/lib/event-processor.mjs +0 -624
- package/scripts/lib/http-wake-deliverer.mjs +0 -31
|
@@ -2,6 +2,9 @@ import {
|
|
|
2
2
|
parseDerivedEventRecord,
|
|
3
3
|
sha256Hex,
|
|
4
4
|
} from '../../dist/src/intelligenceProtocol/index.js';
|
|
5
|
+
import {
|
|
6
|
+
evaluatePatternMeasure,
|
|
7
|
+
} from '../../dist/src/patternV2/evaluator.js';
|
|
5
8
|
|
|
6
9
|
export const DERIVED_EVENT_SERVER_ID = 'event-intelligence:derived';
|
|
7
10
|
export const DERIVED_EVENT_CONNECTION_ID = 'event-intelligence:derived';
|
|
@@ -26,6 +29,7 @@ function evidenceRef(source) {
|
|
|
26
29
|
sourceEventId: source.sourceEventId,
|
|
27
30
|
traceId: source.traceId,
|
|
28
31
|
occurredAt: source.occurredAt,
|
|
32
|
+
...(source.receivedAt ? { receivedAt: source.receivedAt } : {}),
|
|
29
33
|
payloadHash: source.payloadHash ?? null,
|
|
30
34
|
contractVersion:
|
|
31
35
|
source.data?._derived?.contractVersion ?? null,
|
|
@@ -60,6 +64,43 @@ function latestForClause(match, clauseId) {
|
|
|
60
64
|
)[0] ?? null;
|
|
61
65
|
}
|
|
62
66
|
|
|
67
|
+
function patternCandidateForMatch(match) {
|
|
68
|
+
const bindings = {};
|
|
69
|
+
for (const event of match.sourceEvents) {
|
|
70
|
+
bindings[event.clauseId] ??= [];
|
|
71
|
+
bindings[event.clauseId].push(event);
|
|
72
|
+
}
|
|
73
|
+
for (const events of Object.values(bindings)) {
|
|
74
|
+
events.sort((a, b) =>
|
|
75
|
+
Date.parse(a.occurredAt) - Date.parse(b.occurredAt) ||
|
|
76
|
+
a.sourceEventId.localeCompare(b.sourceEventId)
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
const events = [...match.sourceEvents].sort((a, b) =>
|
|
80
|
+
Date.parse(a.occurredAt) - Date.parse(b.occurredAt) ||
|
|
81
|
+
a.sourceEventId.localeCompare(b.sourceEventId)
|
|
82
|
+
);
|
|
83
|
+
return {
|
|
84
|
+
bindings,
|
|
85
|
+
events,
|
|
86
|
+
startIndex: events.length ? 0 : -1,
|
|
87
|
+
endIndex: events.length ? events.length - 1 : -1,
|
|
88
|
+
semanticDecisions: [],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function derivedOccurredAt(match) {
|
|
93
|
+
if (match.patternState?.completedAt) {
|
|
94
|
+
return match.patternState.completedAt;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const candidates = match.sourceEvents
|
|
98
|
+
.map((event) => Date.parse(event.occurredAt))
|
|
99
|
+
.filter(Number.isFinite);
|
|
100
|
+
if (!candidates.length) return match.updatedAt;
|
|
101
|
+
return new Date(Math.max(...candidates)).toISOString();
|
|
102
|
+
}
|
|
103
|
+
|
|
63
104
|
export class DerivedEventCoordinator {
|
|
64
105
|
constructor({
|
|
65
106
|
store,
|
|
@@ -89,7 +130,7 @@ export class DerivedEventCoordinator {
|
|
|
89
130
|
})
|
|
90
131
|
).slice(0, 24)}`;
|
|
91
132
|
|
|
92
|
-
const existing = this.store.getDerivedEvent(
|
|
133
|
+
const existing = await this.store.getDerivedEvent(
|
|
93
134
|
eventId,
|
|
94
135
|
DERIVED_EVENT_SERVER_ID,
|
|
95
136
|
);
|
|
@@ -110,7 +151,7 @@ export class DerivedEventCoordinator {
|
|
|
110
151
|
const rootEvidence = [];
|
|
111
152
|
for (const source of match.sourceEvents) {
|
|
112
153
|
if (source.serverId === DERIVED_EVENT_SERVER_ID) {
|
|
113
|
-
const parent = this.store.getDerivedEvent(
|
|
154
|
+
const parent = await this.store.getDerivedEvent(
|
|
114
155
|
source.sourceEventId,
|
|
115
156
|
DERIVED_EVENT_SERVER_ID,
|
|
116
157
|
);
|
|
@@ -147,7 +188,27 @@ export class DerivedEventCoordinator {
|
|
|
147
188
|
}
|
|
148
189
|
}
|
|
149
190
|
|
|
150
|
-
const
|
|
191
|
+
const measured = {};
|
|
192
|
+
if (spec.measures?.length) {
|
|
193
|
+
const candidate = patternCandidateForMatch(match);
|
|
194
|
+
for (const measure of spec.measures) {
|
|
195
|
+
const value = evaluatePatternMeasure(candidate, measure);
|
|
196
|
+
if (
|
|
197
|
+
value === undefined ||
|
|
198
|
+
(
|
|
199
|
+
value !== null &&
|
|
200
|
+
!['string', 'number', 'boolean'].includes(typeof value)
|
|
201
|
+
)
|
|
202
|
+
) {
|
|
203
|
+
throw new Error(
|
|
204
|
+
`Derived event measure must resolve to scalar or null: ${measure.key}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
measured[measure.key] = value;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const contract = await this.store.getDerivedContract(
|
|
151
212
|
spec.name,
|
|
152
213
|
spec.contractVersion,
|
|
153
214
|
);
|
|
@@ -162,11 +223,12 @@ export class DerivedEventCoordinator {
|
|
|
162
223
|
const data = {
|
|
163
224
|
...(spec.constants ?? {}),
|
|
164
225
|
...projected,
|
|
226
|
+
...measured,
|
|
165
227
|
_derived: {
|
|
166
228
|
triggerId: definition.triggerId,
|
|
167
229
|
triggerVersion: definition.version,
|
|
168
230
|
matchId: match.matchId,
|
|
169
|
-
|
|
231
|
+
partitionKey: match.partitionKey,
|
|
170
232
|
contractVersion: spec.contractVersion,
|
|
171
233
|
schemaFingerprint: contract.schemaFingerprint,
|
|
172
234
|
},
|
|
@@ -176,7 +238,8 @@ export class DerivedEventCoordinator {
|
|
|
176
238
|
traceId: `derived:${eventId}`,
|
|
177
239
|
sourceEventId: eventId,
|
|
178
240
|
name: spec.name,
|
|
179
|
-
occurredAt: match
|
|
241
|
+
occurredAt: derivedOccurredAt(match),
|
|
242
|
+
receivedAt: this.now().toISOString(),
|
|
180
243
|
provider: 'event-intelligence',
|
|
181
244
|
serverId: DERIVED_EVENT_SERVER_ID,
|
|
182
245
|
payloadHash,
|
|
@@ -222,7 +285,7 @@ export class DerivedEventCoordinator {
|
|
|
222
285
|
await sha256Hex({
|
|
223
286
|
kind,
|
|
224
287
|
sourceEventId: record.event.sourceEventId,
|
|
225
|
-
sequence: this.store.auditLength(),
|
|
288
|
+
sequence: await this.store.auditLength(),
|
|
226
289
|
})
|
|
227
290
|
).slice(0, 24)}`,
|
|
228
291
|
traceId: record.event.traceId,
|
|
@@ -26,6 +26,10 @@ import {
|
|
|
26
26
|
PersistentEventStore,
|
|
27
27
|
normalizeEventScopeId,
|
|
28
28
|
} from './persistent-event-store.mjs';
|
|
29
|
+
import {
|
|
30
|
+
assertSharedStoreCapabilities,
|
|
31
|
+
describeStoreCapabilities,
|
|
32
|
+
} from './store-capabilities.mjs';
|
|
29
33
|
import {
|
|
30
34
|
TriggerInspector,
|
|
31
35
|
} from './trigger-inspector.mjs';
|
|
@@ -114,6 +118,23 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
114
118
|
const dataDir = env.DATA_DIR ?? './data';
|
|
115
119
|
const rootStore = providedStore ?? new PersistentEventStore(dataDir);
|
|
116
120
|
const restored = await initializeStore(rootStore);
|
|
121
|
+
const storeCapabilities = describeStoreCapabilities(rootStore);
|
|
122
|
+
const requireSharedStore =
|
|
123
|
+
env.EVENT_INTELLIGENCE_REQUIRE_SHARED_STORE === 'true';
|
|
124
|
+
if (requireSharedStore) {
|
|
125
|
+
assertSharedStoreCapabilities(rootStore);
|
|
126
|
+
if (!String(env.EVENT_INTELLIGENCE_WORKER_ID || '').trim()) {
|
|
127
|
+
const error = new Error(
|
|
128
|
+
'EVENT_INTELLIGENCE_WORKER_ID is required in shared-store mode',
|
|
129
|
+
);
|
|
130
|
+
error.code = 'EVENT_INTELLIGENCE_WORKER_ID_REQUIRED';
|
|
131
|
+
throw error;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const rootWorkerId = String(
|
|
135
|
+
env.EVENT_INTELLIGENCE_WORKER_ID ||
|
|
136
|
+
`ei-${process.pid}`,
|
|
137
|
+
).trim();
|
|
117
138
|
|
|
118
139
|
const evaluator =
|
|
119
140
|
semanticEvaluator !== undefined
|
|
@@ -161,7 +182,17 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
161
182
|
assertStoreContract(store);
|
|
162
183
|
}
|
|
163
184
|
|
|
164
|
-
const triggerEngine = new CompositeTriggerEngine(
|
|
185
|
+
const triggerEngine = new CompositeTriggerEngine(
|
|
186
|
+
store,
|
|
187
|
+
evaluator,
|
|
188
|
+
undefined,
|
|
189
|
+
{
|
|
190
|
+
workerId: `${rootWorkerId}:${scopeId}`,
|
|
191
|
+
partitionLeaseMs: Number(
|
|
192
|
+
env.EVENT_INTELLIGENCE_PARTITION_LEASE_MS ?? 300000,
|
|
193
|
+
),
|
|
194
|
+
},
|
|
195
|
+
);
|
|
165
196
|
const triggerPlanner = new TriggerPlanner({ store });
|
|
166
197
|
const activationHydrator = new ActivationHydrator({ store });
|
|
167
198
|
const wakeCoordinators = new Map();
|
|
@@ -195,7 +226,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
195
226
|
packetBuilder,
|
|
196
227
|
...deliveryOptions,
|
|
197
228
|
deliverer: async (packet) => {
|
|
198
|
-
const activation = activationHydrator.hydrateWake(packet.wake_id);
|
|
229
|
+
const activation = await activationHydrator.hydrateWake(packet.wake_id);
|
|
199
230
|
const result = await handler(packet, activation);
|
|
200
231
|
if (typeof result === 'string' && result) {
|
|
201
232
|
return { runtimeReceiptId: result };
|
|
@@ -224,7 +255,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
224
255
|
packetBuilder,
|
|
225
256
|
...deliveryOptions,
|
|
226
257
|
deliverer: async (packet) => {
|
|
227
|
-
const activation = activationHydrator.hydrateWake(packet.wake_id);
|
|
258
|
+
const activation = await activationHydrator.hydrateWake(packet.wake_id);
|
|
228
259
|
const result = await wake(packet, activation);
|
|
229
260
|
if (typeof result === 'string' && result) {
|
|
230
261
|
return { runtimeReceiptId: result };
|
|
@@ -330,6 +361,7 @@ export async function createLocalEventIntelligenceRuntime({
|
|
|
330
361
|
|
|
331
362
|
return {
|
|
332
363
|
restored,
|
|
364
|
+
storeCapabilities,
|
|
333
365
|
store: defaultContext.store,
|
|
334
366
|
evaluator,
|
|
335
367
|
triggerEngine: defaultContext.triggerEngine,
|
|
@@ -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
|
|
|
@@ -382,7 +394,7 @@ export class McpEventsClientManager {
|
|
|
382
394
|
}
|
|
383
395
|
const removed = this.connections.delete(connectionId);
|
|
384
396
|
if (removed && context) {
|
|
385
|
-
const sources = context.store.listEventSources({
|
|
397
|
+
const sources = await context.store.listEventSources({
|
|
386
398
|
connectionIds: [connectionId],
|
|
387
399
|
});
|
|
388
400
|
for (const source of sources) {
|
|
@@ -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}`,
|
|
@@ -478,9 +486,9 @@ export class McpEventsClientManager {
|
|
|
478
486
|
for (const normalized of descriptors) {
|
|
479
487
|
const sourceId =
|
|
480
488
|
`mcp:${connection.connectionId}:${normalized.name}`;
|
|
481
|
-
const existing =
|
|
482
|
-
.listEventSources()
|
|
483
|
-
|
|
489
|
+
const existing = (
|
|
490
|
+
await context.store.listEventSources()
|
|
491
|
+
).find((source) => source.sourceId === sourceId);
|
|
484
492
|
const same =
|
|
485
493
|
existing &&
|
|
486
494
|
existing.enabled !== false &&
|
|
@@ -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
|
}
|
|
@@ -559,25 +571,26 @@ export class McpEventsClientManager {
|
|
|
559
571
|
return results;
|
|
560
572
|
}
|
|
561
573
|
|
|
562
|
-
desiredSubscriptions(connection, context) {
|
|
574
|
+
async desiredSubscriptions(connection, context) {
|
|
563
575
|
const descriptors = this.descriptors.get(connection.connectionId) || [];
|
|
564
576
|
const byName = new Map(descriptors.map((item) => [item.name, item]));
|
|
565
577
|
const desired = new Map();
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
578
|
+
let triggers = [];
|
|
579
|
+
if (typeof context.triggerControl?.listTriggers === 'function') {
|
|
580
|
+
triggers = await context.triggerControl.listTriggers();
|
|
581
|
+
} else if (typeof context.store?.listTriggers === 'function') {
|
|
582
|
+
const definitions = await context.store.listTriggers();
|
|
583
|
+
triggers = await Promise.all(definitions.map(async (definition) => ({
|
|
584
|
+
definition,
|
|
585
|
+
state:
|
|
586
|
+
typeof context.store.getTriggerState === 'function'
|
|
587
|
+
? await context.store.getTriggerState(
|
|
588
|
+
definition.triggerId,
|
|
589
|
+
definition.version,
|
|
590
|
+
)
|
|
591
|
+
: null,
|
|
592
|
+
})));
|
|
593
|
+
}
|
|
581
594
|
|
|
582
595
|
for (const { definition, state } of triggers) {
|
|
583
596
|
if (state?.status !== 'active') continue;
|
|
@@ -596,12 +609,18 @@ export class McpEventsClientManager {
|
|
|
596
609
|
clause.event,
|
|
597
610
|
args,
|
|
598
611
|
);
|
|
599
|
-
|
|
612
|
+
const consumerRef =
|
|
613
|
+
`${definition.triggerId}@${definition.version}:${clause.id}`;
|
|
614
|
+
const existing = desired.get(subscriptionId);
|
|
615
|
+
if (existing) {
|
|
616
|
+
existing.consumerRefs.push(consumerRef);
|
|
617
|
+
} else {
|
|
600
618
|
desired.set(subscriptionId, {
|
|
601
619
|
subscriptionId,
|
|
602
620
|
eventName: clause.event,
|
|
603
621
|
arguments: args,
|
|
604
622
|
descriptor,
|
|
623
|
+
consumerRefs: [consumerRef],
|
|
605
624
|
});
|
|
606
625
|
}
|
|
607
626
|
}
|
|
@@ -620,6 +639,7 @@ export class McpEventsClientManager {
|
|
|
620
639
|
}
|
|
621
640
|
assertJsonSchemaValue(descriptor.payloadSchema, occurrence.data);
|
|
622
641
|
|
|
642
|
+
const receivedAt = this.now().toISOString();
|
|
623
643
|
const receipt = await context.store.appendMcpOccurrence(
|
|
624
644
|
connection.serverId,
|
|
625
645
|
occurrence,
|
|
@@ -634,6 +654,7 @@ export class McpEventsClientManager {
|
|
|
634
654
|
traceId:
|
|
635
655
|
`mcp:${connection.serverId}:${subscription.subscriptionId}:${occurrence.eventId}`,
|
|
636
656
|
subscriptionArguments: subscription.arguments,
|
|
657
|
+
receivedAt,
|
|
637
658
|
});
|
|
638
659
|
return { accepted: true, occurrence };
|
|
639
660
|
}
|
|
@@ -644,7 +665,7 @@ export class McpEventsClientManager {
|
|
|
644
665
|
subscription,
|
|
645
666
|
patch = {},
|
|
646
667
|
) {
|
|
647
|
-
const previous = context.store.getMcpClientState(
|
|
668
|
+
const previous = await context.store.getMcpClientState(
|
|
648
669
|
connection.connectionId,
|
|
649
670
|
subscription.eventName,
|
|
650
671
|
subscription.arguments,
|
|
@@ -667,7 +688,7 @@ export class McpEventsClientManager {
|
|
|
667
688
|
{ respectSchedule = false } = {},
|
|
668
689
|
) {
|
|
669
690
|
const store = context.store;
|
|
670
|
-
const current = store.getMcpClientState(
|
|
691
|
+
const current = await store.getMcpClientState(
|
|
671
692
|
connection.connectionId,
|
|
672
693
|
subscription.eventName,
|
|
673
694
|
subscription.arguments,
|
|
@@ -683,6 +704,7 @@ export class McpEventsClientManager {
|
|
|
683
704
|
delivery: 'poll',
|
|
684
705
|
status: 'scheduled',
|
|
685
706
|
accepted: 0,
|
|
707
|
+
consumerCount: subscription.consumerRefs.length,
|
|
686
708
|
cursor: current.cursor,
|
|
687
709
|
nextPollAt: current.nextPollAt,
|
|
688
710
|
};
|
|
@@ -697,7 +719,10 @@ export class McpEventsClientManager {
|
|
|
697
719
|
let nextPollMs = connection.pollIntervalMs;
|
|
698
720
|
|
|
699
721
|
do {
|
|
700
|
-
const
|
|
722
|
+
const profile =
|
|
723
|
+
this.profiles.get(connection.connectionId) ??
|
|
724
|
+
getMcpEventsCompatibilityProfile(connection.compatibilityProfile);
|
|
725
|
+
const result = await this.rpc(connection, profile.pollMethod, {
|
|
701
726
|
name: subscription.eventName,
|
|
702
727
|
arguments: subscription.arguments,
|
|
703
728
|
cursor,
|
|
@@ -757,6 +782,7 @@ export class McpEventsClientManager {
|
|
|
757
782
|
delivery: 'poll',
|
|
758
783
|
status: 'ok',
|
|
759
784
|
accepted,
|
|
785
|
+
consumerCount: subscription.consumerRefs.length,
|
|
760
786
|
cursor,
|
|
761
787
|
truncated,
|
|
762
788
|
hasMore,
|
|
@@ -780,11 +806,12 @@ export class McpEventsClientManager {
|
|
|
780
806
|
delivery,
|
|
781
807
|
status: 'active',
|
|
782
808
|
accepted: existing.accepted,
|
|
809
|
+
consumerCount: subscription.consumerRefs.length,
|
|
783
810
|
};
|
|
784
811
|
}
|
|
785
812
|
if (existing) await this.closeSession(subscription.subscriptionId);
|
|
786
813
|
|
|
787
|
-
const current = context.store.getMcpClientState(
|
|
814
|
+
const current = await context.store.getMcpClientState(
|
|
788
815
|
connection.connectionId,
|
|
789
816
|
subscription.eventName,
|
|
790
817
|
subscription.arguments,
|
|
@@ -922,6 +949,7 @@ export class McpEventsClientManager {
|
|
|
922
949
|
delivery,
|
|
923
950
|
status: 'active',
|
|
924
951
|
accepted,
|
|
952
|
+
consumerCount: subscription.consumerRefs.length,
|
|
925
953
|
};
|
|
926
954
|
}
|
|
927
955
|
|
|
@@ -968,7 +996,7 @@ export class McpEventsClientManager {
|
|
|
968
996
|
descriptors = await this.discoverConnection(connectionId);
|
|
969
997
|
}
|
|
970
998
|
|
|
971
|
-
const desired = this.desiredSubscriptions(connection, context);
|
|
999
|
+
const desired = await this.desiredSubscriptions(connection, context);
|
|
972
1000
|
const desiredIds = new Set(desired.keys());
|
|
973
1001
|
const results = [];
|
|
974
1002
|
|
|
@@ -981,6 +1009,7 @@ export class McpEventsClientManager {
|
|
|
981
1009
|
status: 'delivery_not_supported',
|
|
982
1010
|
delivery: null,
|
|
983
1011
|
accepted: 0,
|
|
1012
|
+
consumerCount: subscription.consumerRefs.length,
|
|
984
1013
|
});
|
|
985
1014
|
continue;
|
|
986
1015
|
}
|
|
@@ -1034,12 +1063,12 @@ export class McpEventsClientManager {
|
|
|
1034
1063
|
return results;
|
|
1035
1064
|
}
|
|
1036
1065
|
|
|
1037
|
-
nextDelay(connection) {
|
|
1066
|
+
async nextDelay(connection) {
|
|
1038
1067
|
const context = this.scopeContexts.get(
|
|
1039
1068
|
`${connection.connectionId}::${connection.scopeId}`,
|
|
1040
1069
|
);
|
|
1041
1070
|
if (!context) return connection.pollIntervalMs;
|
|
1042
|
-
const states = context.store.listMcpClientStates(
|
|
1071
|
+
const states = await context.store.listMcpClientStates(
|
|
1043
1072
|
connection.connectionId,
|
|
1044
1073
|
);
|
|
1045
1074
|
const now = this.now().getTime();
|
|
@@ -1073,7 +1102,10 @@ export class McpEventsClientManager {
|
|
|
1073
1102
|
this.started &&
|
|
1074
1103
|
this.connections.get(connection.connectionId) === connection
|
|
1075
1104
|
) {
|
|
1076
|
-
this.scheduleConnection(
|
|
1105
|
+
this.scheduleConnection(
|
|
1106
|
+
connection,
|
|
1107
|
+
await this.nextDelay(connection),
|
|
1108
|
+
);
|
|
1077
1109
|
}
|
|
1078
1110
|
}
|
|
1079
1111
|
}, Math.max(0, delay));
|
|
@@ -1108,19 +1140,27 @@ export class McpEventsClientManager {
|
|
|
1108
1140
|
await this.drain();
|
|
1109
1141
|
}
|
|
1110
1142
|
|
|
1111
|
-
status() {
|
|
1112
|
-
return [...this.connections.values()].map((connection) => {
|
|
1143
|
+
async status() {
|
|
1144
|
+
return Promise.all([...this.connections.values()].map(async (connection) => {
|
|
1113
1145
|
const context = this.scopeContexts.get(
|
|
1114
1146
|
`${connection.connectionId}::${connection.scopeId}`,
|
|
1115
1147
|
);
|
|
1116
1148
|
const store = context?.store ?? this.store;
|
|
1117
|
-
const states = store.listMcpClientStates(connection.connectionId);
|
|
1149
|
+
const states = await store.listMcpClientStates(connection.connectionId);
|
|
1150
|
+
const desired = context
|
|
1151
|
+
? await this.desiredSubscriptions(connection, context)
|
|
1152
|
+
: new Map();
|
|
1118
1153
|
return {
|
|
1119
1154
|
connectionId: connection.connectionId,
|
|
1120
1155
|
scopeId: connection.scopeId,
|
|
1121
1156
|
serverId: connection.serverId,
|
|
1122
1157
|
hostManaged: true,
|
|
1123
|
-
extensionId:
|
|
1158
|
+
extensionId:
|
|
1159
|
+
this.profiles.get(connection.connectionId)?.extensionId ??
|
|
1160
|
+
MCP_EVENTS_EXTENSION_ID,
|
|
1161
|
+
compatibilityProfile:
|
|
1162
|
+
this.profiles.get(connection.connectionId)?.id ??
|
|
1163
|
+
connection.compatibilityProfile,
|
|
1124
1164
|
events: (this.descriptors.get(connection.connectionId) || []).map(
|
|
1125
1165
|
(event) => ({
|
|
1126
1166
|
name: event.name,
|
|
@@ -1130,6 +1170,8 @@ export class McpEventsClientManager {
|
|
|
1130
1170
|
.map((state) => ({
|
|
1131
1171
|
subscriptionId: state.subscriptionId,
|
|
1132
1172
|
arguments: state.arguments ?? {},
|
|
1173
|
+
consumerCount:
|
|
1174
|
+
desired.get(state.subscriptionId)?.consumerRefs.length ?? 0,
|
|
1133
1175
|
delivery: state.deliveryMode,
|
|
1134
1176
|
cursor: state.cursor,
|
|
1135
1177
|
nextPollAt: state.nextPollAt ?? null,
|
|
@@ -1139,6 +1181,6 @@ export class McpEventsClientManager {
|
|
|
1139
1181
|
),
|
|
1140
1182
|
error: this.lastErrors.get(connection.connectionId) ?? null,
|
|
1141
1183
|
};
|
|
1142
|
-
});
|
|
1184
|
+
}));
|
|
1143
1185
|
}
|
|
1144
1186
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
export const MCP_EVENTS_EXTENSION_ID = 'io.modelcontextprotocol/events';
|
|
2
|
+
|
|
3
|
+
export const MCP_EVENTS_COMPATIBILITY_PROFILES = Object.freeze({
|
|
4
|
+
'experimental-extension-2026-09-25': Object.freeze({
|
|
5
|
+
id: 'experimental-extension-2026-09-25',
|
|
6
|
+
capabilityStyle: 'extension',
|
|
7
|
+
extensionId: MCP_EVENTS_EXTENSION_ID,
|
|
8
|
+
listMethod: 'events/list',
|
|
9
|
+
pollMethod: 'events/poll',
|
|
10
|
+
deliveryModes: Object.freeze(['poll', 'push', 'webhook']),
|
|
11
|
+
experimental: true,
|
|
12
|
+
}),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_MCP_EVENTS_COMPATIBILITY_PROFILE =
|
|
16
|
+
'experimental-extension-2026-09-25';
|
|
17
|
+
|
|
18
|
+
export function getMcpEventsCompatibilityProfile(profileId) {
|
|
19
|
+
const id =
|
|
20
|
+
!profileId || profileId === 'auto'
|
|
21
|
+
? DEFAULT_MCP_EVENTS_COMPATIBILITY_PROFILE
|
|
22
|
+
: String(profileId);
|
|
23
|
+
const profile = MCP_EVENTS_COMPATIBILITY_PROFILES[id];
|
|
24
|
+
if (!profile) {
|
|
25
|
+
const error = new Error(`Unknown MCP Events compatibility profile: ${id}`);
|
|
26
|
+
error.code = 'MCP_EVENTS_PROFILE_UNKNOWN';
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
return profile;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function profileAdvertisedByCapabilities(profile, capabilities = {}) {
|
|
33
|
+
if (profile.capabilityStyle !== 'extension') return false;
|
|
34
|
+
const extensions =
|
|
35
|
+
capabilities?.extensions &&
|
|
36
|
+
!Array.isArray(capabilities.extensions) &&
|
|
37
|
+
typeof capabilities.extensions === 'object'
|
|
38
|
+
? capabilities.extensions
|
|
39
|
+
: {};
|
|
40
|
+
return Object.prototype.hasOwnProperty.call(
|
|
41
|
+
extensions,
|
|
42
|
+
profile.extensionId,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveMcpEventsCompatibilityProfile({
|
|
47
|
+
capabilities,
|
|
48
|
+
requested = 'auto',
|
|
49
|
+
} = {}) {
|
|
50
|
+
const profile = getMcpEventsCompatibilityProfile(requested);
|
|
51
|
+
return profileAdvertisedByCapabilities(profile, capabilities)
|
|
52
|
+
? profile
|
|
53
|
+
: null;
|
|
54
|
+
}
|