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.
Files changed (32) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +26 -14
  3. package/SECURITY.md +11 -7
  4. package/bin/mcp-event-intelligence.mjs +2 -2
  5. package/dist/src/composite/matching.d.ts +1 -1
  6. package/dist/src/composite/matching.js +21 -1
  7. package/dist/src/intelligenceProtocol/schemas.d.ts +2 -0
  8. package/dist/src/intelligenceProtocol/schemas.js +1 -0
  9. package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +2 -0
  10. package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +1 -0
  11. package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +6 -0
  12. package/dist/src/intelligenceProtocol/triggerSchemas.js +3 -0
  13. package/dist/src/mcpEvents/consumer.d.ts +1 -0
  14. package/dist/src/mcpEvents/consumer.js +1 -0
  15. package/dist/src/mcpEvents/erpnextProvider.d.ts +0 -2
  16. package/dist/src/mcpEvents/erpnextProvider.js +0 -3
  17. package/dist/src/mcpEvents/provider.d.ts +24 -10
  18. package/dist/src/mcpEvents/provider.js +124 -55
  19. package/dist/src/mcpEvents/server.d.ts +3 -1
  20. package/dist/src/mcpEvents/server.js +20 -19
  21. package/dist/src/protocol/types.d.ts +1 -0
  22. package/package.json +1 -1
  23. package/scripts/host-integration.d.mts +40 -0
  24. package/scripts/lib/composite-event-consumer.mjs +2 -0
  25. package/scripts/lib/json-schema.mjs +134 -0
  26. package/scripts/lib/local-event-intelligence-runtime.mjs +1 -0
  27. package/scripts/lib/mcp-events-client.mjs +760 -253
  28. package/scripts/lib/persistent-event-store.mjs +68 -9
  29. package/scripts/lib/trigger-control-plane.mjs +26 -0
  30. package/scripts/lib/trigger-planner.mjs +23 -0
  31. package/scripts/mcp-stdio-server.mjs +2 -1
  32. package/server.json +2 -2
@@ -1,15 +1,37 @@
1
1
  import { McpEventOccurrenceSchema, } from '../intelligenceProtocol/schemas.js';
2
- export const MCP_EVENTS_CAPABILITY_KEY = 'io.modelcontextprotocol.experimental/events';
2
+ export const MCP_EVENTS_EXTENSION_ID = 'io.modelcontextprotocol/events';
3
+ export const MCP_EVENTS_CAPABILITY_KEY = MCP_EVENTS_EXTENSION_ID;
3
4
  export const MCP_EVENTS_CAPABILITY = Object.freeze({
4
- status: 'draft',
5
- designDate: '2026-02-19',
6
- methods: ['events/list', 'events/poll'],
7
5
  listChanged: false,
8
6
  });
7
+ export const MCP_EVENTS_ERROR = Object.freeze({
8
+ INVALID_PARAMS: -32602,
9
+ METHOD_NOT_FOUND: -32601,
10
+ INTERNAL_ERROR: -32603,
11
+ NOT_FOUND: -32011,
12
+ FORBIDDEN: -32012,
13
+ RESOURCE_EXHAUSTED: -32013,
14
+ UNSUPPORTED: -32014,
15
+ CALLBACK_ENDPOINT_ERROR: -32015,
16
+ });
17
+ export class McpEventsProtocolError extends Error {
18
+ code;
19
+ data;
20
+ constructor(code, message, data) {
21
+ super(message);
22
+ this.name = 'McpEventsProtocolError';
23
+ this.code = code;
24
+ this.data = data;
25
+ }
26
+ }
9
27
  const ALLOWED_DELIVERY = new Set(['poll', 'push', 'webhook']);
28
+ const LIST_CURSOR_PREFIX = 'mcp-events-list-v1:';
29
+ function invalidParams(message, data) {
30
+ throw new McpEventsProtocolError(MCP_EVENTS_ERROR.INVALID_PARAMS, message, data);
31
+ }
10
32
  function assertRecord(value, message) {
11
33
  if (!value || Array.isArray(value) || typeof value !== 'object') {
12
- throw new Error(message);
34
+ invalidParams(message);
13
35
  }
14
36
  return value;
15
37
  }
@@ -17,7 +39,7 @@ function optionalFiniteNumber(value, name) {
17
39
  if (value === undefined)
18
40
  return undefined;
19
41
  if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
20
- throw new Error(`${name} must be a non-negative finite number`);
42
+ invalidParams(`${name} must be a non-negative finite number`);
21
43
  }
22
44
  return value;
23
45
  }
@@ -25,15 +47,18 @@ function normalizeDescriptor(input) {
25
47
  const raw = assertRecord(input, 'MCP event descriptor must be an object');
26
48
  const name = String(raw.name ?? '').trim();
27
49
  if (!name)
28
- throw new Error('MCP event descriptor requires name');
50
+ invalidParams('MCP event descriptor requires name');
29
51
  const deliveryRaw = Array.isArray(raw.delivery) ? raw.delivery : ['poll'];
30
52
  const delivery = deliveryRaw.map((entry) => String(entry));
31
53
  if (delivery.length === 0 ||
32
54
  delivery.some((entry) => !ALLOWED_DELIVERY.has(entry))) {
33
- throw new Error(`MCP event descriptor ${name} has invalid delivery modes`);
55
+ invalidParams(`MCP event descriptor ${name} has invalid delivery modes`);
34
56
  }
57
+ // createMcpEventsProvider is the poll-backed provider adapter. Hosts may
58
+ // consume push/webhook sources through the host delivery hooks, but a server
59
+ // using this adapter must expose poll for the check-since-cursor callback.
35
60
  if (!delivery.includes('poll')) {
36
- throw new Error(`MCP provider event ${name} must advertise poll delivery in v0.1`);
61
+ invalidParams(`MCP provider event ${name} must advertise poll delivery`);
37
62
  }
38
63
  const inputSchema = raw.inputSchema === undefined
39
64
  ? {}
@@ -41,12 +66,16 @@ function normalizeDescriptor(input) {
41
66
  const payloadSchema = raw.payloadSchema === undefined
42
67
  ? {}
43
68
  : assertRecord(raw.payloadSchema, `MCP event descriptor ${name} payloadSchema must be an object`);
69
+ const meta = raw._meta === undefined
70
+ ? undefined
71
+ : assertRecord(raw._meta, `MCP event descriptor ${name} _meta must be an object`);
44
72
  return {
45
73
  name,
46
74
  description: String(raw.description ?? ''),
47
75
  delivery: delivery,
48
76
  inputSchema,
49
77
  payloadSchema,
78
+ ...(meta ? { _meta: meta } : {}),
50
79
  };
51
80
  }
52
81
  function schemaTypes(schema) {
@@ -59,11 +88,11 @@ export function assertJsonSchemaValue(schemaInput, value, path = '$') {
59
88
  const schema = assertRecord(schemaInput, `Invalid JSON Schema at ${path}`);
60
89
  if (Object.prototype.hasOwnProperty.call(schema, 'const') &&
61
90
  !Object.is(schema.const, value)) {
62
- throw new Error(`MCP event payload violates schema at ${path}: const mismatch`);
91
+ throw new Error(`MCP event value violates schema at ${path}: const mismatch`);
63
92
  }
64
93
  if (Array.isArray(schema.enum) &&
65
94
  !schema.enum.some((entry) => Object.is(entry, value))) {
66
- throw new Error(`MCP event payload violates schema at ${path}: value not in enum`);
95
+ throw new Error(`MCP event value violates schema at ${path}: value not in enum`);
67
96
  }
68
97
  const types = schemaTypes(schema);
69
98
  if (types.length > 0) {
@@ -87,23 +116,23 @@ export function assertJsonSchemaValue(schemaInput, value, path = '$') {
87
116
  return false;
88
117
  });
89
118
  if (!matches) {
90
- throw new Error(`MCP event payload violates schema at ${path}: expected ${types.join('|')}`);
119
+ throw new Error(`MCP event value violates schema at ${path}: expected ${types.join('|')}`);
91
120
  }
92
121
  }
93
122
  if (typeof value === 'string') {
94
123
  if (typeof schema.minLength === 'number' && value.length < schema.minLength) {
95
- throw new Error(`MCP event payload violates schema at ${path}: string too short`);
124
+ throw new Error(`MCP event value violates schema at ${path}: string too short`);
96
125
  }
97
126
  if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {
98
- throw new Error(`MCP event payload violates schema at ${path}: string too long`);
127
+ throw new Error(`MCP event value violates schema at ${path}: string too long`);
99
128
  }
100
129
  }
101
130
  if (typeof value === 'number' && Number.isFinite(value)) {
102
131
  if (typeof schema.minimum === 'number' && value < schema.minimum) {
103
- throw new Error(`MCP event payload violates schema at ${path}: below minimum`);
132
+ throw new Error(`MCP event value violates schema at ${path}: below minimum`);
104
133
  }
105
134
  if (typeof schema.maximum === 'number' && value > schema.maximum) {
106
- throw new Error(`MCP event payload violates schema at ${path}: above maximum`);
135
+ throw new Error(`MCP event value violates schema at ${path}: above maximum`);
107
136
  }
108
137
  }
109
138
  if (value !== null && !Array.isArray(value) && typeof value === 'object') {
@@ -113,7 +142,7 @@ export function assertJsonSchemaValue(schemaInput, value, path = '$') {
113
142
  : [];
114
143
  for (const key of required) {
115
144
  if (!Object.prototype.hasOwnProperty.call(record, key)) {
116
- throw new Error(`MCP event payload violates schema at ${path}: missing required property ${key}`);
145
+ throw new Error(`MCP event value violates schema at ${path}: missing required property ${key}`);
117
146
  }
118
147
  }
119
148
  const properties = schema.properties &&
@@ -132,7 +161,7 @@ export function assertJsonSchemaValue(schemaInput, value, path = '$') {
132
161
  if (schema.additionalProperties === false) {
133
162
  for (const key of Object.keys(record)) {
134
163
  if (!Object.prototype.hasOwnProperty.call(properties, key)) {
135
- throw new Error(`MCP event payload violates schema at ${path}: unexpected property ${key}`);
164
+ throw new Error(`MCP event value violates schema at ${path}: unexpected property ${key}`);
136
165
  }
137
166
  }
138
167
  }
@@ -148,7 +177,7 @@ function normalizePollParams(paramsInput) {
148
177
  const params = assertRecord(paramsInput ?? {}, 'events/poll params must be an object');
149
178
  const name = String(params.name ?? '').trim();
150
179
  if (!name)
151
- throw new Error('events/poll requires name');
180
+ invalidParams('events/poll requires name');
152
181
  const cursor = params.cursor === undefined || params.cursor === null
153
182
  ? null
154
183
  : String(params.cursor);
@@ -161,7 +190,7 @@ function normalizePollParams(paramsInput) {
161
190
  !Number.isInteger(maxEventsRaw) ||
162
191
  maxEventsRaw < 1 ||
163
192
  maxEventsRaw > 500) {
164
- throw new Error('maxEvents must be an integer between 1 and 500');
193
+ invalidParams('maxEvents must be an integer between 1 and 500');
165
194
  }
166
195
  return {
167
196
  name,
@@ -171,10 +200,22 @@ function normalizePollParams(paramsInput) {
171
200
  maxEvents: maxEventsRaw,
172
201
  };
173
202
  }
203
+ function encodeListCursor(offset) {
204
+ return `${LIST_CURSOR_PREFIX}${offset}`;
205
+ }
206
+ function decodeListCursor(value) {
207
+ if (typeof value !== 'string' || !value.startsWith(LIST_CURSOR_PREFIX)) {
208
+ invalidParams('events/list cursor is invalid');
209
+ }
210
+ const offset = Number(value.slice(LIST_CURSOR_PREFIX.length));
211
+ if (!Number.isInteger(offset) || offset < 0) {
212
+ invalidParams('events/list cursor is invalid');
213
+ }
214
+ return offset;
215
+ }
174
216
  export class McpEventsProvider {
175
217
  events = new Map();
176
- supportedVersions;
177
- instructions;
218
+ listPageSize;
178
219
  constructor(options) {
179
220
  if (!options || !Array.isArray(options.events)) {
180
221
  throw new Error('MCP Events provider requires events[]');
@@ -192,59 +233,79 @@ export class McpEventsProvider {
192
233
  poll: input.poll,
193
234
  });
194
235
  }
195
- this.supportedVersions =
196
- options.supportedVersions?.map(String).filter(Boolean) ?? ['2026-07-28'];
197
- this.instructions =
198
- options.instructions ??
199
- 'Experimental MCP Events provider compatibility adapter. Events follow the Triggers & Events design sketch; this is not finalized MCP conformance.';
236
+ const pageSize = options.listPageSize ?? 100;
237
+ if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 1000) {
238
+ throw new Error('MCP Events listPageSize must be between 1 and 1000');
239
+ }
240
+ this.listPageSize = pageSize;
200
241
  }
201
242
  canHandle(method) {
202
- return (method === 'server/discover' ||
203
- method === 'events/list' ||
204
- method === 'events/poll');
243
+ return method === 'events/list' || method === 'events/poll';
205
244
  }
206
- listEvents() {
207
- return [...this.events.values()].map(({ descriptor }) => descriptor);
245
+ listEvents(paramsInput) {
246
+ const params = paramsInput === undefined
247
+ ? {}
248
+ : assertRecord(paramsInput, 'events/list params must be an object');
249
+ const offset = params.cursor === undefined || params.cursor === null
250
+ ? 0
251
+ : decodeListCursor(params.cursor);
252
+ const all = [...this.events.values()].map(({ descriptor }) => descriptor);
253
+ if (offset > all.length)
254
+ invalidParams('events/list cursor is out of range');
255
+ const events = all.slice(offset, offset + this.listPageSize);
256
+ const nextOffset = offset + events.length;
257
+ return {
258
+ events,
259
+ ...(nextOffset < all.length
260
+ ? { nextCursor: encodeListCursor(nextOffset) }
261
+ : {}),
262
+ };
208
263
  }
209
264
  async handleRequest(request, context) {
210
265
  try {
211
266
  switch (request.method) {
212
- case 'server/discover':
213
- return this.ok(request.id, {
214
- supportedVersions: this.supportedVersions,
215
- capabilities: {
216
- experimental: {
217
- [MCP_EVENTS_CAPABILITY_KEY]: MCP_EVENTS_CAPABILITY,
218
- },
219
- },
220
- instructions: this.instructions,
221
- });
222
267
  case 'events/list':
223
- return this.ok(request.id, {
224
- events: this.listEvents(),
225
- });
268
+ return this.ok(request.id, this.listEvents(request.params));
226
269
  case 'events/poll':
227
270
  return this.ok(request.id, await this.poll(request.params, context));
228
271
  default:
229
- return this.error(request.id, -32601, `Method not found: ${request.method}`);
272
+ return this.error(request.id, MCP_EVENTS_ERROR.METHOD_NOT_FOUND, `Method not found: ${request.method}`);
230
273
  }
231
274
  }
232
275
  catch (error) {
233
- return this.error(request.id, -32602, error instanceof Error ? error.message : 'Invalid params');
276
+ if (error instanceof McpEventsProtocolError) {
277
+ return this.error(request.id, error.code, error.message, error.data);
278
+ }
279
+ return this.error(request.id, MCP_EVENTS_ERROR.INTERNAL_ERROR, error instanceof Error ? error.message : 'Internal error');
234
280
  }
235
281
  }
236
282
  async poll(paramsInput, context) {
237
283
  const params = normalizePollParams(paramsInput);
238
284
  const registered = this.events.get(params.name);
239
285
  if (!registered) {
240
- throw new Error(`Unknown event: ${params.name}`);
286
+ throw new McpEventsProtocolError(MCP_EVENTS_ERROR.NOT_FOUND, `Unknown event: ${params.name}`, { kind: 'event' });
287
+ }
288
+ try {
289
+ assertJsonSchemaValue(registered.descriptor.inputSchema, params.arguments);
290
+ }
291
+ catch (error) {
292
+ throw new McpEventsProtocolError(MCP_EVENTS_ERROR.INVALID_PARAMS, error instanceof Error ? error.message : 'Invalid event arguments');
241
293
  }
242
- const result = assertRecord(await registered.poll({
294
+ const rawResult = await registered.poll({
243
295
  ...params,
244
296
  context,
245
- }), `MCP provider ${params.name} returned invalid poll result`);
246
- if (typeof result.cursor !== 'string') {
247
- throw new Error(`MCP provider ${params.name} returned poll result without string cursor`);
297
+ });
298
+ if (!rawResult || Array.isArray(rawResult) || typeof rawResult !== 'object') {
299
+ throw new Error(`MCP provider ${params.name} returned invalid poll result`);
300
+ }
301
+ const result = rawResult;
302
+ const cursor = result.cursor === null
303
+ ? null
304
+ : typeof result.cursor === 'string'
305
+ ? result.cursor
306
+ : undefined;
307
+ if (cursor === undefined) {
308
+ throw new Error(`MCP provider ${params.name} returned poll result without string|null cursor`);
248
309
  }
249
310
  if (!Array.isArray(result.events)) {
250
311
  throw new Error(`MCP provider ${params.name} returned poll result without events[]`);
@@ -277,7 +338,7 @@ export class McpEventsProvider {
277
338
  }
278
339
  return {
279
340
  events,
280
- cursor: result.cursor,
341
+ cursor,
281
342
  truncated,
282
343
  hasMore,
283
344
  nextPollMs,
@@ -286,8 +347,16 @@ export class McpEventsProvider {
286
347
  ok(id, result) {
287
348
  return { jsonrpc: '2.0', id, result };
288
349
  }
289
- error(id, code, message) {
290
- return { jsonrpc: '2.0', id, error: { code, message } };
350
+ error(id, code, message, data) {
351
+ return {
352
+ jsonrpc: '2.0',
353
+ id,
354
+ error: {
355
+ code,
356
+ message,
357
+ ...(data === undefined ? {} : { data }),
358
+ },
359
+ };
291
360
  }
292
361
  }
293
362
  export function createMcpEventsProvider(options) {
@@ -2,6 +2,8 @@ import type { EventDescriptor, EventOccurrence, JsonRpcRequest, JsonRpcResponse
2
2
  export interface PersistedMcpOccurrence {
3
3
  sequence: number;
4
4
  serverId: string;
5
+ /** Client-side durable subscription identity when this log is used by EI. */
6
+ subscriptionId?: string | null;
5
7
  event: EventOccurrence;
6
8
  }
7
9
  export interface McpEventOccurrenceStore {
@@ -21,7 +23,7 @@ export interface PollEventsParams {
21
23
  }
22
24
  export interface PollEventsResult {
23
25
  events: EventOccurrence[];
24
- cursor: string;
26
+ cursor: string | null;
25
27
  truncated: boolean;
26
28
  hasMore: boolean;
27
29
  nextPollMs: number;
@@ -26,27 +26,20 @@ export class ExperimentalMcpEventsServer {
26
26
  async handleRequest(request) {
27
27
  try {
28
28
  switch (request.method) {
29
- case 'server/discover':
30
- return this.ok(request.id, {
31
- supportedVersions: ['2026-07-28'],
32
- capabilities: {
33
- experimental: {
34
- 'io.modelcontextprotocol.experimental/events': {
35
- status: 'draft',
36
- designDate: '2026-02-19',
37
- methods: ['events/list', 'events/poll'],
38
- listChanged: false,
39
- },
40
- },
41
- },
42
- instructions: 'Experimental MCP Events provider. Events follow the Triggers & Events WG design sketch; this is not finalized MCP conformance.',
43
- });
44
29
  case 'events/list':
45
30
  return this.ok(request.id, {
46
31
  events: [...this.events.values()].map((entry) => entry.descriptor),
47
32
  });
48
- case 'events/poll':
49
- return this.ok(request.id, this.poll((request.params ?? {})));
33
+ case 'events/poll': {
34
+ const params = (request.params ?? {});
35
+ if (!params.name || typeof params.name !== 'string') {
36
+ return this.error(request.id, -32602, 'events/poll requires name');
37
+ }
38
+ if (!this.events.has(params.name)) {
39
+ return this.error(request.id, -32011, `Unknown event: ${params.name}`, { kind: 'event' });
40
+ }
41
+ return this.ok(request.id, this.poll(params));
42
+ }
50
43
  default:
51
44
  return this.error(request.id, -32601, `Method not found: ${request.method}`);
52
45
  }
@@ -116,7 +109,15 @@ export class ExperimentalMcpEventsServer {
116
109
  ok(id, result) {
117
110
  return { jsonrpc: '2.0', id, result };
118
111
  }
119
- error(id, code, message) {
120
- return { jsonrpc: '2.0', id, error: { code, message } };
112
+ error(id, code, message, data) {
113
+ return {
114
+ jsonrpc: '2.0',
115
+ id,
116
+ error: {
117
+ code,
118
+ message,
119
+ ...(data === undefined ? {} : { data }),
120
+ },
121
+ };
121
122
  }
122
123
  }
@@ -26,6 +26,7 @@ export interface EventDescriptor {
26
26
  delivery: Array<'poll' | 'push' | 'webhook'>;
27
27
  inputSchema: Record<string, unknown>;
28
28
  payloadSchema: Record<string, unknown>;
29
+ _meta?: Record<string, unknown>;
29
30
  }
30
31
  export interface EventOccurrence<TData extends Record<string, unknown> = Record<string, unknown>> {
31
32
  eventId: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-event-intelligence",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Durable temporal event intelligence for agent hosts: host-owned MCP event discovery, composite triggers, derived events, and targeted wakeups.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -11,6 +11,33 @@ export interface HostMcpClientLike {
11
11
  getServerCapabilities?(): unknown;
12
12
  }
13
13
 
14
+ export interface HostMcpEventDeliveryCallbacks {
15
+ subscriptionId: string;
16
+ name: string;
17
+ arguments: Record<string, unknown>;
18
+ cursor: string | null;
19
+ onEvent(event: Record<string, unknown>): Promise<void>;
20
+ onActive(state?: Record<string, unknown>): Promise<void>;
21
+ onGap(state?: Record<string, unknown>): Promise<void>;
22
+ onError(error: unknown): void;
23
+ onTerminated(detail?: unknown): Promise<void>;
24
+ }
25
+
26
+ export type HostMcpEventDeliveryHandle =
27
+ | (() => void | Promise<void>)
28
+ | {
29
+ cursor?: string | null;
30
+ truncated?: boolean;
31
+ close?(): void | Promise<void>;
32
+ cancel?(): void | Promise<void>;
33
+ unsubscribe?(): void | Promise<void>;
34
+ };
35
+
36
+ export type HostMcpEventDeliveryOpener = (
37
+ input: HostMcpEventDeliveryCallbacks,
38
+ ) => HostMcpEventDeliveryHandle | Promise<HostMcpEventDeliveryHandle>;
39
+
40
+
14
41
  export interface HostMcpConnectionOptions {
15
42
  connectionId?: string;
16
43
  id?: string;
@@ -18,6 +45,8 @@ export interface HostMcpConnectionOptions {
18
45
  serverId?: string;
19
46
  client?: HostMcpClientLike;
20
47
  request?: (method: string, params?: unknown) => Promise<unknown>;
48
+ getCapabilities?: () => unknown | Promise<unknown>;
49
+ capabilities?: Record<string, unknown>;
21
50
  enabled?: boolean;
22
51
  /** Isolation partition used by the shared EI host. Defaults to \"default\". */
23
52
  scopeId?: string;
@@ -25,6 +54,12 @@ export interface HostMcpConnectionOptions {
25
54
  maxEvents?: number;
26
55
  /** Maximum number of immediately drained pages when the provider reports hasMore. */
27
56
  maxPollBatches?: number;
57
+ /** Prefer one advertised delivery mode when the host can provide it. */
58
+ preferredDelivery?: 'poll' | 'push' | 'webhook';
59
+ /** Host-owned adapter for events/stream delivery. */
60
+ openEventStream?: HostMcpEventDeliveryOpener;
61
+ /** Host-owned adapter for webhook subscription/receiver delivery. */
62
+ createWebhookSubscription?: HostMcpEventDeliveryOpener;
28
63
  }
29
64
 
30
65
  export interface HostMcpRegistry {
@@ -113,9 +148,14 @@ export function createHostMcpEventsConnection(
113
148
  connectionId: string;
114
149
  serverId: string;
115
150
  request(method: string, params?: unknown): Promise<unknown>;
151
+ getCapabilities(): unknown | Promise<unknown>;
116
152
  enabled: boolean;
117
153
  pollIntervalMs: number;
118
154
  maxEvents: number;
155
+ maxPollBatches: number;
156
+ preferredDelivery: 'poll' | 'push' | 'webhook' | null;
157
+ openEventStream: HostMcpEventDeliveryOpener | null;
158
+ createWebhookSubscription: HostMcpEventDeliveryOpener | null;
119
159
  };
120
160
 
121
161
  export function createEventIntelligenceHost(
@@ -34,6 +34,7 @@ export class CompositeEventConsumer {
34
34
  serverId,
35
35
  provider,
36
36
  traceId,
37
+ subscriptionArguments,
37
38
  }) {
38
39
  const correlatable = await mcpOccurrenceToCorrelatableEvent(
39
40
  event,
@@ -41,6 +42,7 @@ export class CompositeEventConsumer {
41
42
  traceId,
42
43
  ...(provider ? { provider } : {}),
43
44
  ...(serverId ? { serverId } : {}),
45
+ subscriptionArguments: subscriptionArguments ?? {},
44
46
  },
45
47
  );
46
48
 
@@ -0,0 +1,134 @@
1
+ function assertRecord(value, message) {
2
+ if (!value || Array.isArray(value) || typeof value !== 'object') {
3
+ throw new Error(message);
4
+ }
5
+ return value;
6
+ }
7
+
8
+ function schemaTypes(schema) {
9
+ if (Array.isArray(schema?.type)) return schema.type.map(String);
10
+ return schema?.type === undefined ? [] : [String(schema.type)];
11
+ }
12
+
13
+ export function assertJsonSchemaValue(schemaInput, value, path = '$') {
14
+ if (!schemaInput || Array.isArray(schemaInput) || typeof schemaInput !== 'object') {
15
+ return;
16
+ }
17
+ const schema = schemaInput;
18
+
19
+ if (
20
+ Object.prototype.hasOwnProperty.call(schema, 'const') &&
21
+ !Object.is(schema.const, value)
22
+ ) {
23
+ throw new Error(`JSON Schema violation at ${path}: const mismatch`);
24
+ }
25
+
26
+ if (
27
+ Array.isArray(schema.enum) &&
28
+ !schema.enum.some((entry) => Object.is(entry, value))
29
+ ) {
30
+ throw new Error(`JSON Schema violation at ${path}: value not in enum`);
31
+ }
32
+
33
+ const types = schemaTypes(schema);
34
+ if (types.length) {
35
+ const matches = types.some((type) => {
36
+ if (type === 'null') return value === null;
37
+ if (type === 'array') return Array.isArray(value);
38
+ if (type === 'object') {
39
+ return value !== null && !Array.isArray(value) && typeof value === 'object';
40
+ }
41
+ if (type === 'integer') return Number.isInteger(value);
42
+ if (type === 'number') return typeof value === 'number' && Number.isFinite(value);
43
+ if (type === 'string') return typeof value === 'string';
44
+ if (type === 'boolean') return typeof value === 'boolean';
45
+ return false;
46
+ });
47
+ if (!matches) {
48
+ throw new Error(
49
+ `JSON Schema violation at ${path}: expected ${types.join('|')}`,
50
+ );
51
+ }
52
+ }
53
+
54
+ if (typeof value === 'string') {
55
+ if (typeof schema.minLength === 'number' && value.length < schema.minLength) {
56
+ throw new Error(`JSON Schema violation at ${path}: string too short`);
57
+ }
58
+ if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {
59
+ throw new Error(`JSON Schema violation at ${path}: string too long`);
60
+ }
61
+ if (typeof schema.pattern === 'string') {
62
+ const re = new RegExp(schema.pattern);
63
+ if (!re.test(value)) {
64
+ throw new Error(`JSON Schema violation at ${path}: pattern mismatch`);
65
+ }
66
+ }
67
+ }
68
+
69
+ if (typeof value === 'number' && Number.isFinite(value)) {
70
+ if (typeof schema.minimum === 'number' && value < schema.minimum) {
71
+ throw new Error(`JSON Schema violation at ${path}: below minimum`);
72
+ }
73
+ if (typeof schema.maximum === 'number' && value > schema.maximum) {
74
+ throw new Error(`JSON Schema violation at ${path}: above maximum`);
75
+ }
76
+ }
77
+
78
+ if (value !== null && !Array.isArray(value) && typeof value === 'object') {
79
+ const record = value;
80
+ const required = Array.isArray(schema.required)
81
+ ? schema.required.map(String)
82
+ : [];
83
+ for (const key of required) {
84
+ if (!Object.prototype.hasOwnProperty.call(record, key)) {
85
+ throw new Error(
86
+ `JSON Schema violation at ${path}: missing required property ${key}`,
87
+ );
88
+ }
89
+ }
90
+
91
+ const properties =
92
+ schema.properties &&
93
+ !Array.isArray(schema.properties) &&
94
+ typeof schema.properties === 'object'
95
+ ? schema.properties
96
+ : {};
97
+
98
+ for (const [key, childSchema] of Object.entries(properties)) {
99
+ if (
100
+ Object.prototype.hasOwnProperty.call(record, key) &&
101
+ childSchema &&
102
+ !Array.isArray(childSchema) &&
103
+ typeof childSchema === 'object'
104
+ ) {
105
+ assertJsonSchemaValue(childSchema, record[key], `${path}.${key}`);
106
+ }
107
+ }
108
+
109
+ if (schema.additionalProperties === false) {
110
+ for (const key of Object.keys(record)) {
111
+ if (!Object.prototype.hasOwnProperty.call(properties, key)) {
112
+ throw new Error(
113
+ `JSON Schema violation at ${path}: unexpected property ${key}`,
114
+ );
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ if (
121
+ Array.isArray(value) &&
122
+ schema.items &&
123
+ !Array.isArray(schema.items) &&
124
+ typeof schema.items === 'object'
125
+ ) {
126
+ value.forEach((entry, index) =>
127
+ assertJsonSchemaValue(schema.items, entry, `${path}[${index}]`),
128
+ );
129
+ }
130
+ }
131
+
132
+ export function assertJsonObject(value, message) {
133
+ return assertRecord(value, message);
134
+ }
@@ -65,6 +65,7 @@ const REQUIRED_STORE_METHODS = [
65
65
  'appendAudit',
66
66
  'auditLength',
67
67
  'getMcpClientState',
68
+ 'listMcpClientStates',
68
69
  'putMcpClientState',
69
70
  'appendMcpOccurrence',
70
71
  ];