mcp-event-intelligence 0.7.1 → 0.8.1

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.8.1] - 2026-10-05
4
+
5
+ ### Fixed
6
+
7
+ - preserve successful durable trigger creation when host result projection fails after commit, falling back to the canonical inline success payload instead of reporting `EVENT_TRIGGER_CREATE_FAILED`;
8
+ - preserve the legacy `authorize()` callback's normalized error-result behavior while keeping native interrupt/suspension passthrough on the new `control()` contract;
9
+ - add regression coverage for both post-commit projection failure and legacy authorization callback rejection;
10
+ - align the root package-lock metadata with the published package version.
11
+
12
+ ## [0.8.0] - 2026-10-05
13
+
14
+ ### Embedded runtime integration
15
+
16
+ - add `createEmbeddedRuntimeIntegration()` above the lower-level host kit;
17
+ - add a runtime-neutral `EventSourceRegistry` over host-owned event connections;
18
+ - add portable EI capability metadata and a neutral tool catalog for host-owned exposure;
19
+ - add host-owned result projection with optional runtime result references;
20
+ - replace boolean authorization in the new integration surface with a host control contract that can execute or return an opaque deny/approval/interrupt/defer result;
21
+ - preserve native host interrupt/suspension exceptions instead of normalizing them into EI errors;
22
+ - keep tooling optional so headless/workflow runtimes can embed EI without exposing agent tools;
23
+ - document and test the invariant that EI core contains no named-runtime dispatch or per-runtime adapter matrix.
24
+
3
25
  ## [0.7.1] - 2026-10-04
4
26
 
5
27
  ### Fixed
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  [![CI](https://github.com/sarooo17/event-intelligence/actions/workflows/ci.yml/badge.svg)](https://github.com/sarooo17/event-intelligence/actions/workflows/ci.yml)
6
6
  [![npm](https://img.shields.io/npm/v/mcp-event-intelligence.svg)](https://www.npmjs.com/package/mcp-event-intelligence)
7
- [![MCP Registry](https://img.shields.io/badge/MCP%20Registry-v0.7.1-5b5bd6)](https://registry.modelcontextprotocol.io/?q=io.github.sarooo17%2Fevent-intelligence)
7
+ [![MCP Registry](https://img.shields.io/badge/MCP%20Registry-v0.8.1-5b5bd6)](https://registry.modelcontextprotocol.io/?q=io.github.sarooo17%2Fevent-intelligence)
8
8
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
9
9
 
10
10
  MCP Event Intelligence is an experimental event runtime for agents that need to react to **future conditions over multiple event sources** without keeping an LLM or agent loop alive.
@@ -31,53 +31,92 @@ npm install mcp-event-intelligence
31
31
 
32
32
  Published package: [npm](https://www.npmjs.com/package/mcp-event-intelligence) · [Official MCP Registry](https://registry.modelcontextprotocol.io/?q=io.github.sarooo17%2Fevent-intelligence)
33
33
 
34
- For most runtimes, start with the runtime-neutral embedded host kit:
34
+ For most runtimes, start with the runtime-neutral integration surface:
35
35
 
36
36
  ```js
37
37
  import {
38
- createEmbeddedEventIntelligence,
38
+ createEmbeddedRuntimeIntegration,
39
+ createEventSourceRegistry,
39
40
  } from 'mcp-event-intelligence/embedded';
40
41
 
41
- const ei = await createEmbeddedEventIntelligence({
42
+ const ei = await createEmbeddedRuntimeIntegration({
42
43
  dataDir: './data/event-intelligence',
43
44
 
44
- // The host keeps ownership of MCP transports, auth and connection lifecycle.
45
- mcp: {
46
- listConnections: () => runtime.mcp.connections(),
47
- subscribe: (refresh) => runtime.mcp.onConnectionsChanged(refresh),
48
- },
45
+ // The host owns transports, auth and connection lifecycle.
46
+ eventSources: createEventSourceRegistry({
47
+ list: () => runtime.events.connections(),
48
+ subscribe: (refresh) => runtime.events.onConnectionsChanged(refresh),
49
+ }),
49
50
 
50
- // EI produces an activation; the host decides how its own work resumes.
51
+ // EI emits an activation. The host decides what "resume" means.
51
52
  activation: {
52
53
  resolveTarget: (target) => runtime.resolveContinuation(target),
53
54
  hasReceipt: (receiptId) => runtime.hasWakeReceipt(receiptId),
54
55
  deliver: ({ activation, target, receiptId }) =>
55
56
  runtime.resumeFromEvent({ activation, target, receiptId }),
56
57
  },
58
+
59
+ // Optional. Runtimes without an agent-tool surface can omit tooling entirely.
60
+ tooling: {
61
+ resolveContext: (ctx) => ({
62
+ target: runtime.currentContinuation(ctx),
63
+ actor: runtime.currentActor(ctx),
64
+ owner: runtime.currentOwner(ctx),
65
+ }),
66
+
67
+ // Host-owned control. EI does not reduce authorization/approval/interrupt
68
+ // semantics to a boolean.
69
+ control: (request) => runtime.policy.control(request),
70
+
71
+ // Optional host-owned result projection. Large results may be externalized
72
+ // behind a runtime-specific reference instead of entering model context.
73
+ projectResult: (request) => runtime.results.project(request),
74
+ },
57
75
  });
58
76
  ```
59
77
 
60
78
  The continuation target is intentionally opaque to EI apart from
61
79
  `{ runtime, kind, id }`. A host may map it to a Turn, Execution, thread,
62
80
  graph checkpoint, workflow, Case, or any other durable continuation primitive.
63
- The package contains no `runtime === "muffin"` / `"artemis"` branches.
64
81
 
65
- Portable agent-facing trigger tools are optional:
82
+ The returned tools are neutral descriptors with EI-neutral capability metadata:
66
83
 
67
84
  ```js
68
- const tools = ei.createAgentTools({
69
- resolveContext: (ctx) => ({
70
- target: runtime.currentContinuation(ctx),
71
- actor: runtime.currentActor(ctx),
72
- owner: runtime.currentOwner(ctx),
73
- }),
74
- authorize: (request) => runtime.policy.authorize(request),
75
- });
85
+ for (const tool of ei.toolCatalog.all) {
86
+ // The host translates this descriptor into its own tool/capability system.
87
+ runtime.tools.register(tool);
88
+ }
76
89
  ```
77
90
 
78
- Each tool is a neutral `{ name, description, inputSchema, execute }`
79
- descriptor. Adapt that descriptor to OpenAI, Anthropic, an internal capability
80
- graph, or any other host SDK without changing EI core.
91
+ EI intentionally ships **no per-runtime adapter matrix** such as
92
+ `toOpenAI()`, `toAnthropic()`, `toMuffin()` or `toArtemis()`.
93
+ There are too many runtimes, and adding one runtime must never require changing
94
+ EI core. The host consumes the same neutral contract and performs the final
95
+ translation at its own boundary.
96
+
97
+ Tool exposure is also host-owned. `toolCatalog.list({ capabilityIds })` can
98
+ project only the capabilities the host has decided to expose. EI supplies
99
+ metadata such as read vs durable mutation; it does not decide the host's RBAC,
100
+ risk tiers, approval flow or interrupt UX.
101
+
102
+ For durable mutations, `tooling.control()` returns either:
103
+
104
+ - `{ action: 'execute', execution?: ... }` — proceed, optionally attaching
105
+ host policy/approval provenance; or
106
+ - `{ action: 'return', result: ... }` — stop and return an opaque host-owned
107
+ outcome. That outcome may mean denied, approval required, interrupted,
108
+ deferred, or anything else the host runtime supports.
109
+
110
+ EI never interprets the second case. This keeps authorization, human approval
111
+ and interrupt semantics entirely outside the event engine.
112
+
113
+ Result handling follows the same boundary. `projectResult()` may keep a small
114
+ value inline or replace it with a host-owned reference. EI does not become a
115
+ blob store, artifact system or model-context manager.
116
+
117
+ The package contains no named-runtime dispatch such as
118
+ `if (runtime === "muffin")`, `"artemis"`, `"openai"` or `"anthropic"`.
119
+ A new runtime should require only host-boundary code outside EI core.
81
120
 
82
121
  The lower-level host API remains available when a runtime needs direct control:
83
122
 
@@ -1,15 +1,22 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { readFileSync } from 'node:fs';
4
+
5
+ const packageJson = JSON.parse(
6
+ readFileSync(new URL('../package.json', import.meta.url), 'utf8'),
7
+ );
8
+ const packageVersion = String(packageJson.version || '').trim();
9
+
3
10
  const args = process.argv.slice(2);
4
11
  const flags = new Set(args);
5
12
 
6
13
  if (flags.has('--version') || flags.has('-v')) {
7
- console.log('0.6.1');
14
+ console.log(packageVersion);
8
15
  process.exit(0);
9
16
  }
10
17
 
11
18
  if (flags.has('--help') || flags.has('-h')) {
12
- console.log(`MCP Event Intelligence v0.6.1
19
+ console.log(`MCP Event Intelligence v${packageVersion}
13
20
 
14
21
  MCP-native complex event processing for durable agents.
15
22
 
@@ -53,4 +60,4 @@ if (args[0] === 'mcp' || flags.has('--mcp')) {
53
60
  await import('../scripts/mcp-stdio.mjs');
54
61
  } else {
55
62
  await import('../scripts/service.mjs');
56
- }
63
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-event-intelligence",
3
- "version": "0.7.1",
3
+ "version": "0.8.1",
4
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,6 +7,7 @@ export type {
7
7
  ActivationEnvelope as EventActivation,
8
8
  RuntimeTarget as ContinuationTarget,
9
9
  };
10
+
10
11
  import type {
11
12
  EventIntelligenceHost,
12
13
  EventIntelligenceHostOptions,
@@ -25,31 +26,58 @@ export interface EmbeddedHostIdentity {
25
26
  export interface EmbeddedToolContext {
26
27
  scopeId?: string;
27
28
  target: RuntimeTarget;
28
- actor: EmbeddedHostIdentity;
29
- owner: EmbeddedHostIdentity;
29
+ actor?: EmbeddedHostIdentity;
30
+ owner?: EmbeddedHostIdentity;
30
31
  confirmationId?: string;
31
32
  [key: string]: unknown;
32
33
  }
33
34
 
35
+ export interface EventIntelligenceCapabilityMetadata {
36
+ id: string;
37
+ operation: 'read' | 'create' | 'update' | 'delete' | string;
38
+ resource: string;
39
+ effect: 'none' | 'durable-state' | string;
40
+ durability: 'ephemeral' | 'durable' | string;
41
+ hostControl: 'none' | 'required' | string;
42
+ }
43
+
44
+ export interface PortableResultReference {
45
+ id: string;
46
+ kind?: string;
47
+ metadata?: Record<string, unknown>;
48
+ [key: string]: unknown;
49
+ }
50
+
51
+ export interface PortableProjectedResult {
52
+ inline?: unknown;
53
+ reference?: PortableResultReference;
54
+ summary?: string;
55
+ metadata?: Record<string, unknown>;
56
+ }
57
+
34
58
  export interface PortableToolResult {
35
59
  ok: boolean;
36
60
  data?: any;
61
+ result?: PortableProjectedResult;
37
62
  error?: {
38
63
  code: string;
39
64
  message: string;
40
65
  };
66
+ [key: string]: unknown;
41
67
  }
42
68
 
43
69
  export interface PortableAgentTool<RuntimeContext = unknown> {
44
70
  name: string;
45
71
  description: string;
46
72
  inputSchema: Record<string, unknown>;
73
+ capability: EventIntelligenceCapabilityMetadata;
47
74
  execute(
48
75
  args: unknown,
49
76
  runtimeContext: RuntimeContext,
50
77
  ): Promise<PortableToolResult>;
51
78
  }
52
79
 
80
+ /** @deprecated Prefer the host-owned control() contract. */
53
81
  export interface AuthorizationDecision {
54
82
  allowed: boolean;
55
83
  code?: string;
@@ -59,6 +87,33 @@ export interface AuthorizationDecision {
59
87
  confirmationId?: string;
60
88
  }
61
89
 
90
+ export interface HostControlExecuteDecision {
91
+ action: 'execute';
92
+ execution?: {
93
+ actor?: EmbeddedHostIdentity;
94
+ owner?: EmbeddedHostIdentity;
95
+ /** Host policy/approval receipt persisted by EI as confirmation provenance. */
96
+ receiptId?: string;
97
+ /** Legacy alias accepted for compatibility. */
98
+ confirmationId?: string;
99
+ [key: string]: unknown;
100
+ };
101
+ }
102
+
103
+ export interface HostControlReturnDecision {
104
+ action: 'return';
105
+ /**
106
+ * Opaque host-owned outcome. It may represent deny, approval required,
107
+ * interruption, deferral or another runtime-specific control path.
108
+ * EI does not interpret the reason.
109
+ */
110
+ result: PortableToolResult;
111
+ }
112
+
113
+ export type HostControlDecision =
114
+ | HostControlExecuteDecision
115
+ | HostControlReturnDecision;
116
+
62
117
  export interface ActivationDeliveryInput<Target = unknown> {
63
118
  packet: Record<string, unknown>;
64
119
  activation: ActivationEnvelope;
@@ -84,16 +139,69 @@ export interface ActivationDispatcherOptions<Target = RuntimeTarget> {
84
139
  ): HostWakeReceipt | string | void | Promise<HostWakeReceipt | string | void>;
85
140
  }
86
141
 
142
+ export type EventSourceConnection = HostMcpConnectionOptions;
143
+
144
+ export interface EventSourceRegistry {
145
+ list(): Iterable<EventSourceConnection> | Promise<Iterable<EventSourceConnection>>;
146
+ subscribe?: (listener: () => void) => void | (() => void);
147
+ }
148
+
149
+ export interface EventSourceRegistryOptions {
150
+ list(): Iterable<EventSourceConnection> | Promise<Iterable<EventSourceConnection>>;
151
+ subscribe?: (listener: () => void) => void | (() => void);
152
+ }
153
+
154
+ export interface PortableToolCatalog<RuntimeContext = unknown> {
155
+ readonly all: readonly PortableAgentTool<RuntimeContext>[];
156
+ get(name: string): PortableAgentTool<RuntimeContext> | null;
157
+ list(options?: {
158
+ capabilityIds?: Iterable<string>;
159
+ }): PortableAgentTool<RuntimeContext>[];
160
+ capabilities(): EventIntelligenceCapabilityMetadata[];
161
+ }
162
+
87
163
  export interface EventIntelligenceAgentToolsOptions<RuntimeContext = unknown> {
88
164
  host: EventIntelligenceHost;
89
165
  resolveContext(
90
166
  runtimeContext: RuntimeContext,
91
167
  request: {
92
168
  action: 'event.sources.list' | 'trigger.create';
169
+ capability: EventIntelligenceCapabilityMetadata;
93
170
  input?: unknown;
94
171
  },
95
172
  ): EmbeddedToolContext | Promise<EmbeddedToolContext>;
96
- authorize(input: {
173
+
174
+ /**
175
+ * Host-owned control gate for durable mutations.
176
+ *
177
+ * Return action:'execute' to allow EI to perform the mutation, or
178
+ * action:'return' with a fully host-owned PortableToolResult to represent
179
+ * deny/interrupt/approval/defer semantics without EI interpreting them.
180
+ */
181
+ control?(input: {
182
+ capability: EventIntelligenceCapabilityMetadata;
183
+ action: 'trigger.create';
184
+ runtimeContext: RuntimeContext;
185
+ context: EmbeddedToolContext;
186
+ input: unknown;
187
+ plan: any;
188
+ }): HostControlDecision | Promise<HostControlDecision>;
189
+
190
+ /**
191
+ * Host-owned result projection. Use it to inline, summarize or externalize
192
+ * large results behind a runtime-specific reference.
193
+ */
194
+ projectResult?(input: {
195
+ capability: EventIntelligenceCapabilityMetadata;
196
+ runtimeContext: RuntimeContext;
197
+ context: EmbeddedToolContext;
198
+ value: unknown;
199
+ }): PortableProjectedResult | void | Promise<PortableProjectedResult | void>;
200
+
201
+ /**
202
+ * @deprecated Compatibility only. New integrations should use control().
203
+ */
204
+ authorize?(input: {
97
205
  action: 'trigger.create';
98
206
  runtimeContext: RuntimeContext;
99
207
  context: EmbeddedToolContext;
@@ -103,6 +211,7 @@ export interface EventIntelligenceAgentToolsOptions<RuntimeContext = unknown> {
103
211
  | boolean
104
212
  | AuthorizationDecision
105
213
  | Promise<boolean | AuthorizationDecision>;
214
+
106
215
  names?: {
107
216
  sources?: string;
108
217
  create?: string;
@@ -136,6 +245,45 @@ export interface EmbeddedEventIntelligence<RuntimeContext = unknown> {
136
245
  close(): Promise<void>;
137
246
  }
138
247
 
248
+ export interface EmbeddedRuntimeTooling<RuntimeContext = unknown> {
249
+ resolveContext: EventIntelligenceAgentToolsOptions<RuntimeContext>['resolveContext'];
250
+ control: NonNullable<EventIntelligenceAgentToolsOptions<RuntimeContext>['control']>;
251
+ projectResult?: EventIntelligenceAgentToolsOptions<RuntimeContext>['projectResult'];
252
+ names?: {
253
+ sources?: string;
254
+ create?: string;
255
+ };
256
+ }
257
+
258
+ export interface EmbeddedRuntimeIntegrationOptions<RuntimeContext = unknown>
259
+ extends Omit<
260
+ EmbeddedEventIntelligenceOptions<RuntimeContext>,
261
+ 'mcpRegistry' | 'mcp' | 'agentTools'
262
+ > {
263
+ eventSources?: EventSourceRegistry | EventSourceRegistryOptions;
264
+ tooling?: EmbeddedRuntimeTooling<RuntimeContext>;
265
+ }
266
+
267
+ export interface EmbeddedRuntimeIntegration<RuntimeContext = unknown>
268
+ extends Omit<EmbeddedEventIntelligence<RuntimeContext>, 'tools'> {
269
+ readonly tools: readonly PortableAgentTool<RuntimeContext>[];
270
+ readonly toolCatalog: PortableToolCatalog<RuntimeContext>;
271
+ readonly capabilities: readonly EventIntelligenceCapabilityMetadata[];
272
+ readonly eventSources: EventSourceRegistry | null;
273
+ }
274
+
275
+ export function createResultReference(
276
+ input: PortableResultReference,
277
+ ): PortableResultReference;
278
+
279
+ export function createEventSourceRegistry(
280
+ options: EventSourceRegistryOptions,
281
+ ): EventSourceRegistry;
282
+
283
+ export function createPortableToolCatalog<RuntimeContext = unknown>(
284
+ tools?: PortableAgentTool<RuntimeContext>[],
285
+ ): PortableToolCatalog<RuntimeContext>;
286
+
139
287
  export function createContinuationTarget(input: {
140
288
  runtime: string;
141
289
  kind: string;
@@ -154,6 +302,15 @@ export function createEmbeddedEventIntelligence<RuntimeContext = unknown>(
154
302
  options?: EmbeddedEventIntelligenceOptions<RuntimeContext>,
155
303
  ): Promise<EmbeddedEventIntelligence<RuntimeContext>>;
156
304
 
305
+ export function createEmbeddedRuntimeIntegration<RuntimeContext = unknown>(
306
+ options: EmbeddedRuntimeIntegrationOptions<RuntimeContext>,
307
+ ): Promise<EmbeddedRuntimeIntegration<RuntimeContext>>;
308
+
309
+ export const EVENT_INTELLIGENCE_CAPABILITIES: Readonly<{
310
+ eventSourcesList: Readonly<EventIntelligenceCapabilityMetadata>;
311
+ triggerCreate: Readonly<EventIntelligenceCapabilityMetadata>;
312
+ }>;
313
+
157
314
  export const PORTABLE_EVENT_INTELLIGENCE_TOOL_NAMES: Readonly<{
158
315
  sources: 'event_sources_list';
159
316
  create: 'trigger_create';
@@ -13,6 +13,25 @@ const DEFAULT_TOOL_NAMES = Object.freeze({
13
13
  create: 'trigger_create',
14
14
  });
15
15
 
16
+ export const EVENT_INTELLIGENCE_CAPABILITIES = Object.freeze({
17
+ eventSourcesList: Object.freeze({
18
+ id: 'event-intelligence.event-sources.list',
19
+ operation: 'read',
20
+ resource: 'event-source',
21
+ effect: 'none',
22
+ durability: 'ephemeral',
23
+ hostControl: 'none',
24
+ }),
25
+ triggerCreate: Object.freeze({
26
+ id: 'event-intelligence.trigger.create',
27
+ operation: 'create',
28
+ resource: 'trigger',
29
+ effect: 'durable-state',
30
+ durability: 'durable',
31
+ hostControl: 'required',
32
+ }),
33
+ });
34
+
16
35
  const TriggerClauseInput = z.object({
17
36
  id: z.string().min(1).max(200).optional(),
18
37
  event: z.string().min(1).max(200),
@@ -140,15 +159,121 @@ function normalizeIdentity(input, label) {
140
159
  };
141
160
  }
142
161
 
143
- function normalizeAuthorization(decision) {
144
- if (decision === true) return { allowed: true };
145
- if (decision === false || decision == null) return { allowed: false };
162
+ function normalizeLegacyAuthorization(decision) {
163
+ if (decision === true) return { action: 'execute' };
164
+ if (decision === false || decision == null) {
165
+ return {
166
+ action: 'return',
167
+ result: {
168
+ ok: false,
169
+ error: {
170
+ code: 'EVENT_TRIGGER_AUTHORIZATION_DENIED',
171
+ message: 'Host denied durable trigger creation',
172
+ },
173
+ },
174
+ };
175
+ }
146
176
  if (typeof decision !== 'object') {
147
177
  throw new Error('authorize() must return boolean or an authorization decision');
148
178
  }
179
+ if (decision.allowed === true) {
180
+ return {
181
+ action: 'execute',
182
+ execution: {
183
+ actor: decision.actor,
184
+ owner: decision.owner,
185
+ receiptId: decision.confirmationId,
186
+ },
187
+ };
188
+ }
189
+ return {
190
+ action: 'return',
191
+ result: {
192
+ ok: false,
193
+ error: {
194
+ code: String(decision.code || 'EVENT_TRIGGER_AUTHORIZATION_DENIED'),
195
+ message: String(
196
+ decision.message || 'Host denied durable trigger creation',
197
+ ),
198
+ },
199
+ },
200
+ };
201
+ }
202
+
203
+ function normalizeHostControlDecision(decision) {
204
+ if (!decision || typeof decision !== 'object') {
205
+ throw new Error(
206
+ 'Host control must return { action: "execute" } or { action: "return", result }',
207
+ );
208
+ }
209
+ if (decision.action === 'return') {
210
+ if (!decision.result || typeof decision.result.ok !== 'boolean') {
211
+ throw new Error('Host control return action requires a portable tool result');
212
+ }
213
+ return decision;
214
+ }
215
+ if (decision.action !== 'execute') {
216
+ throw new Error(
217
+ 'Host control action must be "execute" or "return"',
218
+ );
219
+ }
220
+ return {
221
+ action: 'execute',
222
+ execution:
223
+ decision.execution && typeof decision.execution === 'object'
224
+ ? decision.execution
225
+ : {},
226
+ };
227
+ }
228
+
229
+ export function createResultReference(reference) {
230
+ if (reference == null) return undefined;
231
+ if (typeof reference !== 'object') {
232
+ throw new Error('result reference must be an object');
233
+ }
234
+ const id = String(reference.id || '').trim();
235
+ if (!id) throw new Error('result reference id is required');
149
236
  return {
150
- ...decision,
151
- allowed: decision.allowed === true,
237
+ ...reference,
238
+ id,
239
+ ...(reference.kind == null
240
+ ? {}
241
+ : { kind: String(reference.kind).trim() || undefined }),
242
+ };
243
+ }
244
+
245
+ async function projectPortableResult(projectResult, input) {
246
+ if (typeof projectResult !== 'function') {
247
+ return { ok: true, data: input.value };
248
+ }
249
+
250
+ const projected = await projectResult(input);
251
+ if (projected == null) {
252
+ return { ok: true, data: input.value };
253
+ }
254
+ if (typeof projected !== 'object') {
255
+ throw new Error('projectResult() must return an object');
256
+ }
257
+
258
+ const reference = createResultReference(projected.reference);
259
+ const hasInline = Object.prototype.hasOwnProperty.call(projected, 'inline');
260
+ if (!hasInline && !reference) {
261
+ throw new Error('projectResult() must return inline and/or reference');
262
+ }
263
+
264
+ return {
265
+ ok: true,
266
+ ...(hasInline ? { data: projected.inline } : {}),
267
+ result: {
268
+ ...(hasInline ? { inline: projected.inline } : {}),
269
+ ...(reference ? { reference } : {}),
270
+ ...(projected.summary == null
271
+ ? {}
272
+ : { summary: String(projected.summary) }),
273
+ ...(projected.metadata && typeof projected.metadata === 'object'
274
+ ? { metadata: projected.metadata }
275
+ : {}),
276
+ },
152
277
  };
153
278
  }
154
279
 
@@ -176,6 +301,53 @@ function wakeIdFromPacket(packet) {
176
301
  return typeof value === 'string' && value.trim() ? value : null;
177
302
  }
178
303
 
304
+ /**
305
+ * Runtime-neutral registry over host-owned event connections.
306
+ *
307
+ * The host keeps credentials, transport and lifecycle ownership. EI only asks
308
+ * for the currently available connections and listens for topology changes.
309
+ */
310
+ export function createEventSourceRegistry({
311
+ list,
312
+ subscribe,
313
+ } = {}) {
314
+ if (typeof list !== 'function') {
315
+ throw new Error('Event source registry requires list()');
316
+ }
317
+ if (subscribe !== undefined && typeof subscribe !== 'function') {
318
+ throw new Error('Event source registry subscribe must be a function');
319
+ }
320
+
321
+ return Object.freeze({
322
+ list,
323
+ ...(subscribe ? { subscribe } : {}),
324
+ });
325
+ }
326
+
327
+ /**
328
+ * Neutral catalog for host tool exposure. It does not decide policy or adapt
329
+ * descriptors to any vendor SDK; the embedding runtime owns both.
330
+ */
331
+ export function createPortableToolCatalog(tools = []) {
332
+ const all = Object.freeze([...tools]);
333
+ const byName = new Map(all.map((tool) => [tool.name, tool]));
334
+
335
+ return Object.freeze({
336
+ all,
337
+ get(name) {
338
+ return byName.get(name) ?? null;
339
+ },
340
+ list({ capabilityIds } = {}) {
341
+ if (!capabilityIds) return [...all];
342
+ const allowed = new Set(capabilityIds);
343
+ return all.filter((tool) => allowed.has(tool.capability?.id));
344
+ },
345
+ capabilities() {
346
+ return all.map((tool) => tool.capability).filter(Boolean);
347
+ },
348
+ });
349
+ }
350
+
179
351
  /**
180
352
  * A runtime-neutral continuation target. EI persists and returns this value
181
353
  * without assigning semantics to runtime/kind/id.
@@ -338,13 +510,14 @@ function buildPlanInput(parsed, target) {
338
510
  }
339
511
 
340
512
  /**
341
- * Portable agent-facing tools. The descriptors do not depend on OpenAI,
342
- * Anthropic, MCP tool objects, Muffin, Artemis, LangGraph, or any other host.
343
- * Adapters only need to translate {name, description, inputSchema, execute}.
513
+ * Portable agent-facing tools. The descriptors are runtime-neutral.
514
+ * The embedding host performs any final translation into its own tool system.
344
515
  */
345
516
  export function createEventIntelligenceAgentTools({
346
517
  host,
347
518
  resolveContext,
519
+ control,
520
+ projectResult,
348
521
  authorize,
349
522
  names = {},
350
523
  } = {}) {
@@ -354,8 +527,19 @@ export function createEventIntelligenceAgentTools({
354
527
  if (typeof resolveContext !== 'function') {
355
528
  throw new Error('Agent tools require resolveContext()');
356
529
  }
357
- if (typeof authorize !== 'function') {
358
- throw new Error('Agent tools require authorize() for durable mutations');
530
+ if (control !== undefined && typeof control !== 'function') {
531
+ throw new Error('Agent tools control must be a function');
532
+ }
533
+ if (authorize !== undefined && typeof authorize !== 'function') {
534
+ throw new Error('Agent tools authorize must be a function');
535
+ }
536
+ if (!control && !authorize) {
537
+ throw new Error(
538
+ 'Durable mutations require a host control() callback',
539
+ );
540
+ }
541
+ if (projectResult !== undefined && typeof projectResult !== 'function') {
542
+ throw new Error('Agent tools projectResult must be a function');
359
543
  }
360
544
 
361
545
  const toolNames = {
@@ -363,30 +547,40 @@ export function createEventIntelligenceAgentTools({
363
547
  ...names,
364
548
  };
365
549
 
550
+ const sourcesCapability =
551
+ EVENT_INTELLIGENCE_CAPABILITIES.eventSourcesList;
552
+ const createCapability =
553
+ EVENT_INTELLIGENCE_CAPABILITIES.triggerCreate;
554
+
366
555
  return [
367
556
  {
368
557
  name: toolNames.sources,
369
558
  description:
370
- 'List live future-event sources discovered from MCP connections already owned by the host runtime. Use this before creating a trigger so event names and schemas are not guessed.',
559
+ 'List live future-event sources discovered from event connections already owned by the host runtime. Use this before creating a trigger so event names and schemas are not guessed.',
371
560
  inputSchema: SOURCES_INPUT_SCHEMA,
561
+ capability: sourcesCapability,
372
562
  async execute(_args, runtimeContext) {
373
563
  try {
374
564
  const resolved = await resolveContext(runtimeContext, {
375
565
  action: 'event.sources.list',
566
+ capability: sourcesCapability,
376
567
  });
377
568
  if (typeof host.refreshMcpRegistry === 'function') {
378
569
  await host.refreshMcpRegistry();
379
570
  }
380
571
  const scoped = await scopedHostFor(host, resolved);
381
572
  const sources = await Promise.resolve(scoped.eventSources);
382
- return {
383
- ok: true,
384
- data: {
385
- sources: Array.isArray(sources)
386
- ? sources.map(sourceSummary)
387
- : [],
388
- },
573
+ const value = {
574
+ sources: Array.isArray(sources)
575
+ ? sources.map(sourceSummary)
576
+ : [],
389
577
  };
578
+ return await projectPortableResult(projectResult, {
579
+ capability: sourcesCapability,
580
+ runtimeContext,
581
+ context: resolved,
582
+ value,
583
+ });
390
584
  } catch (error) {
391
585
  return fail(error, 'EVENT_SOURCES_LIST_FAILED');
392
586
  }
@@ -395,8 +589,9 @@ export function createEventIntelligenceAgentTools({
395
589
  {
396
590
  name: toolNames.create,
397
591
  description:
398
- 'Create a durable future condition. The host, not the model, supplies the continuation target and authority context; Event Intelligence wakes that target only when the condition matches.',
592
+ 'Create a durable future condition. The host supplies continuation, control and identity context; Event Intelligence wakes the opaque continuation target only when the condition matches.',
399
593
  inputSchema: CREATE_INPUT_SCHEMA,
594
+ capability: createCapability,
400
595
  async execute(args, runtimeContext) {
401
596
  const parsed = TriggerCreateInput.safeParse(args ?? {});
402
597
  if (!parsed.success) {
@@ -411,53 +606,81 @@ export function createEventIntelligenceAgentTools({
411
606
  };
412
607
  }
413
608
 
609
+ let resolved;
610
+ let scoped;
611
+ let plan;
414
612
  try {
415
- const resolved = await resolveContext(runtimeContext, {
613
+ resolved = await resolveContext(runtimeContext, {
416
614
  action: 'trigger.create',
615
+ capability: createCapability,
417
616
  input: parsed.data,
418
617
  });
419
618
  const target = createContinuationTarget(resolved?.target);
420
619
  if (typeof host.refreshMcpRegistry === 'function') {
421
620
  await host.refreshMcpRegistry();
422
621
  }
423
- const scoped = await scopedHostFor(host, resolved);
424
- const plan = await scoped.planTrigger(
622
+ scoped = await scopedHostFor(host, resolved);
623
+ plan = await scoped.planTrigger(
425
624
  buildPlanInput(parsed.data, target),
426
625
  );
626
+ } catch (error) {
627
+ return fail(error, 'EVENT_TRIGGER_PLAN_FAILED');
628
+ }
427
629
 
428
- const decision = normalizeAuthorization(
429
- await authorize({
430
- action: 'trigger.create',
431
- runtimeContext,
432
- context: resolved,
433
- input: parsed.data,
434
- plan,
435
- }),
436
- );
437
- if (!decision.allowed) {
438
- return {
439
- ok: false,
440
- error: {
441
- code: String(
442
- decision.code || 'EVENT_TRIGGER_AUTHORIZATION_DENIED',
443
- ),
444
- message: String(
445
- decision.message || 'Host denied durable trigger creation',
446
- ),
447
- },
448
- };
630
+ // Host control is intentionally outside EI's error-normalization
631
+ // boundary. A runtime may return an opaque result or throw its native
632
+ // interrupt/suspension signal; EI must not reinterpret that mechanism.
633
+ //
634
+ // The deprecated authorize() path keeps its historical behavior:
635
+ // ordinary callback failures are normalized into PortableToolResult
636
+ // errors instead of becoming rejected tool executions.
637
+ let rawDecision;
638
+ if (control) {
639
+ rawDecision = await control({
640
+ capability: createCapability,
641
+ action: 'trigger.create',
642
+ runtimeContext,
643
+ context: resolved,
644
+ input: parsed.data,
645
+ plan,
646
+ });
647
+ } else {
648
+ try {
649
+ rawDecision = normalizeLegacyAuthorization(
650
+ await authorize({
651
+ action: 'trigger.create',
652
+ runtimeContext,
653
+ context: resolved,
654
+ input: parsed.data,
655
+ plan,
656
+ }),
657
+ );
658
+ } catch (error) {
659
+ return fail(error, 'EVENT_TRIGGER_CREATE_FAILED');
449
660
  }
661
+ }
662
+ const decision = normalizeHostControlDecision(rawDecision);
663
+
664
+ if (decision.action === 'return') {
665
+ return decision.result;
666
+ }
450
667
 
668
+ let value;
669
+ try {
670
+ const execution = decision.execution ?? {};
451
671
  const actor = normalizeIdentity(
452
- decision.actor ?? resolved?.actor,
672
+ execution.actor ?? resolved?.actor,
453
673
  'actor',
454
674
  );
455
675
  const owner = normalizeIdentity(
456
- decision.owner ?? resolved?.owner,
676
+ execution.owner ?? resolved?.owner,
457
677
  'owner',
458
678
  );
459
679
  const confirmationId = String(
460
- decision.confirmationId ?? resolved?.confirmationId ?? '',
680
+ execution.receiptId ??
681
+ execution.confirmationId ??
682
+ resolved?.confirmationId ??
683
+ '',
461
684
  ).trim();
462
685
 
463
686
  const created = await scoped.triggerControl.createTrigger({
@@ -468,20 +691,32 @@ export function createEventIntelligenceAgentTools({
468
691
  ...(confirmationId ? { confirmationId } : {}),
469
692
  });
470
693
 
471
- return {
472
- ok: true,
473
- data: {
474
- receiptId: created.receiptId,
475
- triggerId: created.definition?.triggerId,
476
- version: created.definition?.version,
477
- state: created.state,
478
- connectionIds: plan.connectionIds,
479
- warnings: plan.warnings ?? [],
480
- },
694
+ value = {
695
+ receiptId: created.receiptId,
696
+ triggerId: created.definition?.triggerId,
697
+ version: created.definition?.version,
698
+ state: created.state,
699
+ connectionIds: plan.connectionIds,
700
+ warnings: plan.warnings ?? [],
481
701
  };
482
702
  } catch (error) {
483
703
  return fail(error, 'EVENT_TRIGGER_CREATE_FAILED');
484
704
  }
705
+
706
+ // Trigger creation is already durable at this point. Projection is a
707
+ // host presentation concern, so a projection failure must never turn
708
+ // the committed mutation into an apparent create failure that callers
709
+ // may retry. Fall back to the canonical inline success payload.
710
+ try {
711
+ return await projectPortableResult(projectResult, {
712
+ capability: createCapability,
713
+ runtimeContext,
714
+ context: resolved,
715
+ value,
716
+ });
717
+ } catch {
718
+ return { ok: true, data: value };
719
+ }
485
720
  },
486
721
  },
487
722
  ];
@@ -564,4 +799,91 @@ export async function createEmbeddedEventIntelligence({
564
799
  };
565
800
  }
566
801
 
567
- export const PORTABLE_EVENT_INTELLIGENCE_TOOL_NAMES = DEFAULT_TOOL_NAMES;
802
+
803
+ /**
804
+ * Runtime-level composition over the lower-level embedded host kit.
805
+ *
806
+ * This is deliberately not an adapter for any named runtime. It returns
807
+ * neutral tool descriptors + capability metadata; the embedding host decides
808
+ * how those descriptors are exposed to its model/tool system.
809
+ */
810
+ export async function createEmbeddedRuntimeIntegration({
811
+ dataDir,
812
+ store,
813
+ env = {},
814
+ eventSources,
815
+ mcpClients = [],
816
+ semanticEvaluator,
817
+ activation,
818
+ wake,
819
+ wakeHandlers = {},
820
+ tooling,
821
+ createHost = createEventIntelligenceHost,
822
+ } = {}) {
823
+ if (tooling !== undefined && (!tooling || typeof tooling !== 'object')) {
824
+ throw new Error('tooling must be an object when provided');
825
+ }
826
+ if (tooling && typeof tooling.resolveContext !== 'function') {
827
+ throw new Error('tooling.resolveContext() is required');
828
+ }
829
+ if (tooling && typeof tooling.control !== 'function') {
830
+ throw new Error(
831
+ 'tooling.control() is required for host-owned mutation control',
832
+ );
833
+ }
834
+ if (
835
+ tooling?.projectResult !== undefined &&
836
+ typeof tooling.projectResult !== 'function'
837
+ ) {
838
+ throw new Error('tooling.projectResult must be a function');
839
+ }
840
+
841
+ const sourceRegistry = eventSources
842
+ ? createEventSourceRegistry(eventSources)
843
+ : null;
844
+
845
+ const embedded = await createEmbeddedEventIntelligence({
846
+ dataDir,
847
+ store,
848
+ env,
849
+ ...(sourceRegistry
850
+ ? {
851
+ mcpRegistry: createMcpRegistryAdapter({
852
+ listConnections: () => sourceRegistry.list(),
853
+ ...(typeof sourceRegistry.subscribe === 'function'
854
+ ? { subscribe: (listener) => sourceRegistry.subscribe(listener) }
855
+ : {}),
856
+ }),
857
+ }
858
+ : {}),
859
+ mcpClients,
860
+ semanticEvaluator,
861
+ activation,
862
+ wake,
863
+ wakeHandlers,
864
+ createHost,
865
+ });
866
+
867
+ const tools = tooling
868
+ ? createEventIntelligenceAgentTools({
869
+ host: embedded.host,
870
+ resolveContext: tooling.resolveContext,
871
+ control: tooling.control,
872
+ ...(tooling.projectResult
873
+ ? { projectResult: tooling.projectResult }
874
+ : {}),
875
+ names: tooling.names ?? {},
876
+ })
877
+ : [];
878
+ const toolCatalog = createPortableToolCatalog(tools);
879
+
880
+ return Object.freeze({
881
+ ...embedded,
882
+ tools: toolCatalog.all,
883
+ toolCatalog,
884
+ capabilities: Object.freeze(toolCatalog.capabilities()),
885
+ eventSources: sourceRegistry,
886
+ });
887
+ }
888
+
889
+ export const PORTABLE_EVENT_INTELLIGENCE_TOOL_NAMES = DEFAULT_TOOL_NAMES;
package/server.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.sarooo17/event-intelligence",
4
4
  "title": "MCP Event Intelligence",
5
5
  "description": "MCP-native CEP for durable agents: event-time patterns, semantic predicates, derived events.",
6
- "version": "0.7.1",
6
+ "version": "0.8.1",
7
7
  "repository": {
8
8
  "url": "https://github.com/sarooo17/event-intelligence",
9
9
  "source": "github"
@@ -13,7 +13,7 @@
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "mcp-event-intelligence",
16
- "version": "0.7.1",
16
+ "version": "0.8.1",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },