mcp-event-intelligence 0.3.1 → 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.
Files changed (51) hide show
  1. package/CHANGELOG.md +81 -0
  2. package/README.md +123 -23
  3. package/SECURITY.md +4 -4
  4. package/bin/mcp-event-intelligence.mjs +3 -3
  5. package/dist/src/composite/engine.d.ts +6 -0
  6. package/dist/src/composite/engine.js +599 -10
  7. package/dist/src/composite/matching.d.ts +1 -1
  8. package/dist/src/composite/matching.js +66 -1
  9. package/dist/src/intelligenceProtocol/activationSchemas.d.ts +2 -0
  10. package/dist/src/intelligenceProtocol/activationSchemas.js +1 -0
  11. package/dist/src/intelligenceProtocol/index.d.ts +9 -2
  12. package/dist/src/intelligenceProtocol/index.js +5 -1
  13. package/dist/src/intelligenceProtocol/operatorRegistry.d.ts +567 -0
  14. package/dist/src/intelligenceProtocol/operatorRegistry.js +477 -0
  15. package/dist/src/intelligenceProtocol/patternV2Schemas.d.ts +188 -0
  16. package/dist/src/intelligenceProtocol/patternV2Schemas.js +801 -0
  17. package/dist/src/intelligenceProtocol/schemas.d.ts +6 -0
  18. package/dist/src/intelligenceProtocol/schemas.js +3 -0
  19. package/dist/src/intelligenceProtocol/triggerPlanSchemas.d.ts +179 -4
  20. package/dist/src/intelligenceProtocol/triggerPlanSchemas.js +38 -4
  21. package/dist/src/intelligenceProtocol/triggerSchemas.d.ts +262 -0
  22. package/dist/src/intelligenceProtocol/triggerSchemas.js +164 -2
  23. package/dist/src/mcpEvents/consumer.d.ts +2 -0
  24. package/dist/src/mcpEvents/consumer.js +2 -0
  25. package/dist/src/mcpEvents/erpnextProvider.d.ts +0 -2
  26. package/dist/src/mcpEvents/erpnextProvider.js +0 -3
  27. package/dist/src/mcpEvents/provider.d.ts +24 -10
  28. package/dist/src/mcpEvents/provider.js +124 -55
  29. package/dist/src/mcpEvents/server.d.ts +3 -1
  30. package/dist/src/mcpEvents/server.js +20 -19
  31. package/dist/src/patternV2/evaluator.d.ts +43 -0
  32. package/dist/src/patternV2/evaluator.js +927 -0
  33. package/dist/src/patternV2/legacyCompiler.d.ts +3 -0
  34. package/dist/src/patternV2/legacyCompiler.js +184 -0
  35. package/dist/src/protocol/types.d.ts +1 -0
  36. package/package.json +5 -2
  37. package/scripts/host-integration.d.mts +47 -0
  38. package/scripts/host-integration.mjs +9 -0
  39. package/scripts/lib/activation-hydrator.mjs +1 -0
  40. package/scripts/lib/composite-event-consumer.mjs +4 -0
  41. package/scripts/lib/derived-event-coordinator.mjs +75 -1
  42. package/scripts/lib/json-schema.mjs +134 -0
  43. package/scripts/lib/local-event-intelligence-runtime.mjs +1 -0
  44. package/scripts/lib/mcp-events-client.mjs +798 -253
  45. package/scripts/lib/mcp-events-compatibility.mjs +54 -0
  46. package/scripts/lib/persistent-event-store.mjs +68 -9
  47. package/scripts/lib/trigger-control-plane.mjs +180 -0
  48. package/scripts/lib/trigger-inspector.mjs +15 -0
  49. package/scripts/lib/trigger-planner.mjs +213 -12
  50. package/scripts/mcp-stdio-server.mjs +38 -52
  51. 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
+ }
@@ -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,7 +1,7 @@
1
1
  {
2
2
  "name": "mcp-event-intelligence",
3
- "version": "0.3.1",
4
- "description": "Durable temporal event intelligence for agent hosts: host-owned MCP event discovery, composite triggers, derived events, and targeted wakeups.",
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",
@@ -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,14 @@ 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
+ /** Versioned MCP Events wire profile. Defaults to auto/current experimental snapshot. */
60
+ compatibilityProfile?: 'auto' | 'experimental-extension-2026-09-25' | string;
61
+ /** Host-owned adapter for events/stream delivery. */
62
+ openEventStream?: HostMcpEventDeliveryOpener;
63
+ /** Host-owned adapter for webhook subscription/receiver delivery. */
64
+ createWebhookSubscription?: HostMcpEventDeliveryOpener;
28
65
  }
29
66
 
30
67
  export interface HostMcpRegistry {
@@ -78,6 +115,10 @@ export interface EventIntelligenceScopedHost {
78
115
  readonly triggerPlanner: any;
79
116
  readonly activationHydrator: any;
80
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>;
81
122
  planTrigger(input: TriggerPlanInput): Promise<{
82
123
  planVersion: '1';
83
124
  definition: any;
@@ -113,9 +154,15 @@ export function createHostMcpEventsConnection(
113
154
  connectionId: string;
114
155
  serverId: string;
115
156
  request(method: string, params?: unknown): Promise<unknown>;
157
+ getCapabilities(): unknown | Promise<unknown>;
116
158
  enabled: boolean;
117
159
  pollIntervalMs: number;
118
160
  maxEvents: number;
161
+ maxPollBatches: number;
162
+ preferredDelivery: 'poll' | 'push' | 'webhook' | null;
163
+ compatibilityProfile: string;
164
+ openEventStream: HostMcpEventDeliveryOpener | null;
165
+ createWebhookSubscription: HostMcpEventDeliveryOpener | null;
119
166
  };
120
167
 
121
168
  export function createEventIntelligenceHost(
@@ -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
  }));
@@ -34,6 +34,8 @@ export class CompositeEventConsumer {
34
34
  serverId,
35
35
  provider,
36
36
  traceId,
37
+ subscriptionArguments,
38
+ receivedAt,
37
39
  }) {
38
40
  const correlatable = await mcpOccurrenceToCorrelatableEvent(
39
41
  event,
@@ -41,6 +43,8 @@ export class CompositeEventConsumer {
41
43
  traceId,
42
44
  ...(provider ? { provider } : {}),
43
45
  ...(serverId ? { serverId } : {}),
46
+ subscriptionArguments: subscriptionArguments ?? {},
47
+ ...(receivedAt ? { receivedAt } : {}),
44
48
  },
45
49
  );
46
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.updatedAt,
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,
@@ -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
  ];