@revealui/ai 0.2.9 → 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 (82) hide show
  1. package/README.md +200 -26
  2. package/dist/a2a/handler.d.ts +3 -1
  3. package/dist/a2a/handler.d.ts.map +1 -1
  4. package/dist/a2a/handler.js +24 -7
  5. package/dist/a2a/task-store.d.ts.map +1 -1
  6. package/dist/a2a/task-store.js +5 -1
  7. package/dist/client/hooks/useAgentStream.d.ts +84 -1
  8. package/dist/client/hooks/useAgentStream.d.ts.map +1 -1
  9. package/dist/client/hooks/useAgentStream.js +106 -10
  10. package/dist/index.d.ts +4 -6
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +4 -15
  13. package/dist/ingestion/admin-indexer.js +1 -1
  14. package/dist/ingestion/pipeline.d.ts +1 -1
  15. package/dist/ingestion/pipeline.d.ts.map +1 -1
  16. package/dist/llm/client.d.ts +33 -10
  17. package/dist/llm/client.d.ts.map +1 -1
  18. package/dist/llm/client.js +70 -57
  19. package/dist/llm/key-validator.d.ts.map +1 -1
  20. package/dist/llm/key-validator.js +0 -27
  21. package/dist/llm/providers/inference-snaps.d.ts +7 -5
  22. package/dist/llm/providers/inference-snaps.d.ts.map +1 -1
  23. package/dist/llm/providers/inference-snaps.js +7 -5
  24. package/dist/llm/providers/openai-compat.d.ts +1 -1
  25. package/dist/llm/providers/openai-compat.js +1 -1
  26. package/dist/llm/response-cache.d.ts +1 -2
  27. package/dist/llm/response-cache.d.ts.map +1 -1
  28. package/dist/llm/response-cache.js +1 -2
  29. package/dist/llm/server.d.ts +1 -1
  30. package/dist/llm/server.d.ts.map +1 -1
  31. package/dist/llm/server.js +3 -1
  32. package/dist/memory/crdt/lww-register.js +1 -1
  33. package/dist/memory/preferences/user-preferences-manager.d.ts.map +1 -1
  34. package/dist/memory/preferences/user-preferences-manager.js +1 -1
  35. package/dist/memory/services/index.d.ts +2 -0
  36. package/dist/memory/services/index.d.ts.map +1 -1
  37. package/dist/memory/services/index.js +1 -0
  38. package/dist/memory/services/reconciliation-service.d.ts +64 -0
  39. package/dist/memory/services/reconciliation-service.d.ts.map +1 -0
  40. package/dist/memory/services/reconciliation-service.js +127 -0
  41. package/dist/memory/stores/working-memory.d.ts.map +1 -1
  42. package/dist/memory/stores/working-memory.js +1 -1
  43. package/dist/memory/utils/index.d.ts +1 -2
  44. package/dist/memory/utils/index.d.ts.map +1 -1
  45. package/dist/memory/utils/index.js +1 -2
  46. package/dist/memory/utils/sql-helpers.d.ts +21 -66
  47. package/dist/memory/utils/sql-helpers.d.ts.map +1 -1
  48. package/dist/memory/utils/sql-helpers.js +34 -169
  49. package/dist/memory/vector/vector-memory-service.d.ts.map +1 -1
  50. package/dist/memory/vector/vector-memory-service.js +6 -1
  51. package/dist/orchestration/runtime.d.ts +30 -1
  52. package/dist/orchestration/runtime.d.ts.map +1 -1
  53. package/dist/orchestration/runtime.js +20 -3
  54. package/dist/orchestration/streaming-runtime.d.ts +48 -1
  55. package/dist/orchestration/streaming-runtime.d.ts.map +1 -1
  56. package/dist/orchestration/ticket-agent.d.ts +10 -1
  57. package/dist/orchestration/ticket-agent.d.ts.map +1 -1
  58. package/dist/orchestration/ticket-agent.js +11 -2
  59. package/dist/skills/catalog/vercel-catalog.d.ts.map +1 -1
  60. package/dist/skills/catalog/vercel-catalog.js +13 -7
  61. package/dist/tools/mcp-adapter.d.ts +192 -1
  62. package/dist/tools/mcp-adapter.d.ts.map +1 -1
  63. package/dist/tools/mcp-adapter.js +434 -1
  64. package/dist/tools/mcp-elicitation.d.ts +128 -0
  65. package/dist/tools/mcp-elicitation.d.ts.map +1 -0
  66. package/dist/tools/mcp-elicitation.js +121 -0
  67. package/dist/tools/mcp-events.d.ts +270 -0
  68. package/dist/tools/mcp-events.d.ts.map +1 -0
  69. package/dist/tools/mcp-events.js +165 -0
  70. package/dist/tools/mcp-sampling.d.ts +155 -0
  71. package/dist/tools/mcp-sampling.d.ts.map +1 -0
  72. package/dist/tools/mcp-sampling.js +171 -0
  73. package/dist/tools/ticket-tools.d.ts +35 -2
  74. package/dist/tools/ticket-tools.d.ts.map +1 -1
  75. package/dist/tools/ticket-tools.js +32 -2
  76. package/package.json +38 -12
  77. package/dist/llm/providers/vultr.d.ts +0 -18
  78. package/dist/llm/providers/vultr.d.ts.map +0 -1
  79. package/dist/llm/providers/vultr.js +0 -169
  80. package/dist/memory/utils/deep-clone.d.ts +0 -10
  81. package/dist/memory/utils/deep-clone.d.ts.map +0 -1
  82. package/dist/memory/utils/deep-clone.js +0 -9
@@ -1,9 +1,418 @@
1
1
  /**
2
2
  * MCP Tool Adapter
3
3
  *
4
- * Bridges MCP (Model Context Protocol) servers to the tool system
4
+ * Bridges MCP (Model Context Protocol) servers to the tool system.
5
+ *
6
+ * Two paths:
7
+ *
8
+ * 1. **Standard MCP client** (Stage 5.1a, preferred) — consumers construct an
9
+ * `McpClient` from `@revealui/mcp/client` against stdio or Streamable HTTP,
10
+ * and pass it to `createToolsFromMcpClient()` (or to `AgentRuntime` via
11
+ * `mcpClients`). This is the full-protocol path: future stages will extend
12
+ * this to resources, prompts, sampling, elicitation, etc.
13
+ *
14
+ * 2. **Hypervisor** (legacy, pre-5.1a) — consumers pass an `MCPHypervisor` (or
15
+ * any `MCPToolSource`) to `discoverMCPTools()`. Kept for backwards compat;
16
+ * deprecated in favor of path (1). The hypervisor still owns server-side
17
+ * subprocess + tenant-scoping concerns and isn't going away.
18
+ *
19
+ * Both paths are deliberately **structurally typed** — `@revealui/ai` has no
20
+ * runtime dependency on `@revealui/mcp`. Consumers satisfy the shapes
21
+ * `McpClientLike` / `MCPToolSource` with whichever client they construct.
5
22
  */
6
23
  import { z } from 'zod/v4';
24
+ import { emitMcpEvent } from './mcp-events.js';
25
+ /**
26
+ * Connect the agent's tool registry to a standard MCP client. Lists tools
27
+ * from the MCP server and returns agent-side `Tool` instances that dispatch
28
+ * calls back through the client. Safe to call multiple times per server —
29
+ * each invocation re-reads the current tool list.
30
+ *
31
+ * @example
32
+ * ```typescript
33
+ * import { McpClient } from '@revealui/mcp/client';
34
+ * import { createToolsFromMcpClient } from '@revealui/ai';
35
+ *
36
+ * const client = new McpClient({
37
+ * clientInfo: { name: 'my-agent', version: '1.0.0' },
38
+ * transport: { kind: 'streamable-http', url: 'https://example.com/mcp' },
39
+ * });
40
+ * await client.connect();
41
+ *
42
+ * const tools = await createToolsFromMcpClient(client, {
43
+ * namespace: 'example-server',
44
+ * });
45
+ *
46
+ * agent.tools.push(...tools);
47
+ * ```
48
+ */
49
+ export async function createToolsFromMcpClient(client, options) {
50
+ if (!(options.namespace && /^[a-zA-Z0-9_-]+$/.test(options.namespace))) {
51
+ throw new Error(`createToolsFromMcpClient: namespace must be a non-empty string of [a-zA-Z0-9_-], got ${JSON.stringify(options.namespace)}`);
52
+ }
53
+ const category = options.category ?? 'mcp';
54
+ const includeTools = options.include?.tools !== false;
55
+ const includeResources = options.include?.resources !== false;
56
+ const includePrompts = options.include?.prompts !== false;
57
+ const ctx = {
58
+ namespace: options.namespace,
59
+ category,
60
+ ...(options.onProgress !== undefined ? { onProgress: options.onProgress } : {}),
61
+ ...(options.signal !== undefined ? { signal: options.signal } : {}),
62
+ ...(options.onEvent !== undefined ? { onEvent: options.onEvent } : {}),
63
+ };
64
+ const tools = [];
65
+ // --- Server tools (Stage 5.1a) -----------------------------------------
66
+ if (includeTools) {
67
+ const descriptors = await client.listTools();
68
+ for (const descriptor of descriptors) {
69
+ tools.push(buildServerTool(client, descriptor, ctx));
70
+ }
71
+ }
72
+ // --- Resource meta-tools (Stage 5.1b) ----------------------------------
73
+ if (includeResources && client.listResources && client.readResource) {
74
+ tools.push(buildListResourcesTool(client, ctx));
75
+ tools.push(buildReadResourceTool(client, ctx));
76
+ }
77
+ // --- Prompt meta-tools (Stage 5.1b) ------------------------------------
78
+ if (includePrompts && client.listPrompts && client.getPrompt) {
79
+ tools.push(buildListPromptsTool(client, ctx));
80
+ tools.push(buildGetPromptTool(client, ctx));
81
+ }
82
+ return tools;
83
+ }
84
+ /**
85
+ * Build the request-options object passed to `client.callTool()` /
86
+ * `readResource()` / etc. Wraps the ctx's `onProgress` with the
87
+ * specific tool name + namespace so every emitted event is attributable.
88
+ */
89
+ function buildRequestOptions(ctx, toolName) {
90
+ const opts = {};
91
+ if (ctx.onProgress) {
92
+ const emit = ctx.onProgress;
93
+ opts.onProgress = (progress) => emit({ namespace: ctx.namespace, toolName, progress });
94
+ }
95
+ if (ctx.signal)
96
+ opts.signal = ctx.signal;
97
+ return opts.onProgress || opts.signal ? opts : undefined;
98
+ }
99
+ function buildServerTool(client, descriptor, ctx) {
100
+ const zodSchema = jsonSchemaObjectToZod(descriptor.inputSchema);
101
+ const namespacedName = `mcp_${ctx.namespace}__${descriptor.name}`;
102
+ return {
103
+ name: namespacedName,
104
+ label: descriptor.name,
105
+ description: descriptor.description ?? `${ctx.namespace}: ${descriptor.name}`,
106
+ parameters: zodSchema,
107
+ async execute(params) {
108
+ const validated = zodSchema.parse(params);
109
+ const started = Date.now();
110
+ try {
111
+ const result = await client.callTool(descriptor.name, validated, buildRequestOptions(ctx, descriptor.name));
112
+ if (result.isError) {
113
+ const errorText = extractErrorText(result);
114
+ emitMcpEvent(ctx.onEvent, {
115
+ kind: 'mcp.tool.call',
116
+ namespace: ctx.namespace,
117
+ toolName: descriptor.name,
118
+ duration_ms: Date.now() - started,
119
+ success: false,
120
+ error: errorText,
121
+ });
122
+ return { success: false, error: errorText };
123
+ }
124
+ emitMcpEvent(ctx.onEvent, {
125
+ kind: 'mcp.tool.call',
126
+ namespace: ctx.namespace,
127
+ toolName: descriptor.name,
128
+ duration_ms: Date.now() - started,
129
+ success: true,
130
+ });
131
+ const payload = result.structuredContent ?? result.content;
132
+ return { success: true, data: serializeMCPResult(payload) };
133
+ }
134
+ catch (error) {
135
+ const message = error instanceof Error ? error.message : String(error);
136
+ emitMcpEvent(ctx.onEvent, {
137
+ kind: 'mcp.tool.call',
138
+ namespace: ctx.namespace,
139
+ toolName: descriptor.name,
140
+ duration_ms: Date.now() - started,
141
+ success: false,
142
+ error: message,
143
+ });
144
+ return { success: false, error: message };
145
+ }
146
+ },
147
+ getMetadata() {
148
+ return { category: ctx.category, version: '1.0.0', mcpNamespace: ctx.namespace };
149
+ },
150
+ };
151
+ }
152
+ function buildListResourcesTool(client, ctx) {
153
+ return {
154
+ name: `mcp_${ctx.namespace}__list_resources`,
155
+ label: 'list_resources',
156
+ description: `List resources exposed by the ${ctx.namespace} MCP server. Returns an array of { uri, name, description?, mimeType? }.`,
157
+ parameters: z.object({}),
158
+ async execute() {
159
+ const started = Date.now();
160
+ try {
161
+ const resources = (await client.listResources?.(buildRequestOptions(ctx, 'list_resources'))) ?? [];
162
+ emitMcpEvent(ctx.onEvent, {
163
+ kind: 'mcp.resource.list',
164
+ namespace: ctx.namespace,
165
+ duration_ms: Date.now() - started,
166
+ success: true,
167
+ resourceCount: resources.length,
168
+ });
169
+ return { success: true, data: serializeMCPResult(resources) };
170
+ }
171
+ catch (error) {
172
+ const message = error instanceof Error ? error.message : String(error);
173
+ emitMcpEvent(ctx.onEvent, {
174
+ kind: 'mcp.resource.list',
175
+ namespace: ctx.namespace,
176
+ duration_ms: Date.now() - started,
177
+ success: false,
178
+ error: message,
179
+ });
180
+ return { success: false, error: message };
181
+ }
182
+ },
183
+ getMetadata() {
184
+ return {
185
+ category: ctx.category,
186
+ version: '1.0.0',
187
+ mcpNamespace: ctx.namespace,
188
+ kind: 'resources',
189
+ };
190
+ },
191
+ };
192
+ }
193
+ function buildReadResourceTool(client, ctx) {
194
+ const paramsSchema = z.object({
195
+ uri: z.string().min(1).describe('Resource URI to read (e.g. revealui-content://posts/abc)'),
196
+ });
197
+ return {
198
+ name: `mcp_${ctx.namespace}__read_resource`,
199
+ label: 'read_resource',
200
+ description: `Read a resource by URI from the ${ctx.namespace} MCP server. Returns the resource contents (text parts flattened to a joined string when possible).`,
201
+ parameters: paramsSchema,
202
+ async execute(params) {
203
+ const { uri } = paramsSchema.parse(params);
204
+ const started = Date.now();
205
+ try {
206
+ const contents = (await client.readResource?.(uri, buildRequestOptions(ctx, 'read_resource'))) ?? [];
207
+ emitMcpEvent(ctx.onEvent, {
208
+ kind: 'mcp.resource.read',
209
+ namespace: ctx.namespace,
210
+ uri,
211
+ duration_ms: Date.now() - started,
212
+ success: true,
213
+ });
214
+ const joinedText = flattenResourceText(contents);
215
+ const base = {
216
+ success: true,
217
+ data: serializeMCPResult(contents),
218
+ };
219
+ return joinedText !== undefined ? { ...base, content: joinedText } : base;
220
+ }
221
+ catch (error) {
222
+ const message = error instanceof Error ? error.message : String(error);
223
+ emitMcpEvent(ctx.onEvent, {
224
+ kind: 'mcp.resource.read',
225
+ namespace: ctx.namespace,
226
+ uri,
227
+ duration_ms: Date.now() - started,
228
+ success: false,
229
+ error: message,
230
+ });
231
+ return { success: false, error: message };
232
+ }
233
+ },
234
+ getMetadata() {
235
+ return {
236
+ category: ctx.category,
237
+ version: '1.0.0',
238
+ mcpNamespace: ctx.namespace,
239
+ kind: 'resources',
240
+ };
241
+ },
242
+ };
243
+ }
244
+ function buildListPromptsTool(client, ctx) {
245
+ return {
246
+ name: `mcp_${ctx.namespace}__list_prompts`,
247
+ label: 'list_prompts',
248
+ description: `List prompts exposed by the ${ctx.namespace} MCP server. Returns an array of { name, description?, arguments? }.`,
249
+ parameters: z.object({}),
250
+ async execute() {
251
+ const started = Date.now();
252
+ try {
253
+ const prompts = (await client.listPrompts?.(buildRequestOptions(ctx, 'list_prompts'))) ?? [];
254
+ emitMcpEvent(ctx.onEvent, {
255
+ kind: 'mcp.prompt.list',
256
+ namespace: ctx.namespace,
257
+ duration_ms: Date.now() - started,
258
+ success: true,
259
+ promptCount: prompts.length,
260
+ });
261
+ return { success: true, data: serializeMCPResult(prompts) };
262
+ }
263
+ catch (error) {
264
+ const message = error instanceof Error ? error.message : String(error);
265
+ emitMcpEvent(ctx.onEvent, {
266
+ kind: 'mcp.prompt.list',
267
+ namespace: ctx.namespace,
268
+ duration_ms: Date.now() - started,
269
+ success: false,
270
+ error: message,
271
+ });
272
+ return { success: false, error: message };
273
+ }
274
+ },
275
+ getMetadata() {
276
+ return {
277
+ category: ctx.category,
278
+ version: '1.0.0',
279
+ mcpNamespace: ctx.namespace,
280
+ kind: 'prompts',
281
+ };
282
+ },
283
+ };
284
+ }
285
+ function buildGetPromptTool(client, ctx) {
286
+ const paramsSchema = z.object({
287
+ name: z.string().min(1).describe('Prompt name to retrieve'),
288
+ args: z
289
+ .record(z.string(), z.string())
290
+ .optional()
291
+ .describe('Prompt arguments as a string-valued map (per MCP spec)'),
292
+ });
293
+ return {
294
+ name: `mcp_${ctx.namespace}__get_prompt`,
295
+ label: 'get_prompt',
296
+ description: `Get a resolved prompt from the ${ctx.namespace} MCP server. Returns { description?, messages } — messages is an array of { role, content }.`,
297
+ parameters: paramsSchema,
298
+ async execute(params) {
299
+ const { name, args } = paramsSchema.parse(params);
300
+ const started = Date.now();
301
+ try {
302
+ const result = await client.getPrompt?.(name, args, buildRequestOptions(ctx, 'get_prompt'));
303
+ if (!result) {
304
+ const msg = 'client does not implement getPrompt';
305
+ emitMcpEvent(ctx.onEvent, {
306
+ kind: 'mcp.prompt.get',
307
+ namespace: ctx.namespace,
308
+ promptName: name,
309
+ duration_ms: Date.now() - started,
310
+ success: false,
311
+ error: msg,
312
+ });
313
+ return { success: false, error: msg };
314
+ }
315
+ emitMcpEvent(ctx.onEvent, {
316
+ kind: 'mcp.prompt.get',
317
+ namespace: ctx.namespace,
318
+ promptName: name,
319
+ duration_ms: Date.now() - started,
320
+ success: true,
321
+ });
322
+ const joinedText = flattenPromptMessages(result.messages);
323
+ const base = {
324
+ success: true,
325
+ data: serializeMCPResult(result),
326
+ };
327
+ return joinedText !== undefined ? { ...base, content: joinedText } : base;
328
+ }
329
+ catch (error) {
330
+ const message = error instanceof Error ? error.message : String(error);
331
+ emitMcpEvent(ctx.onEvent, {
332
+ kind: 'mcp.prompt.get',
333
+ namespace: ctx.namespace,
334
+ promptName: name,
335
+ duration_ms: Date.now() - started,
336
+ success: false,
337
+ error: message,
338
+ });
339
+ return { success: false, error: message };
340
+ }
341
+ },
342
+ getMetadata() {
343
+ return {
344
+ category: ctx.category,
345
+ version: '1.0.0',
346
+ mcpNamespace: ctx.namespace,
347
+ kind: 'prompts',
348
+ };
349
+ },
350
+ };
351
+ }
352
+ /**
353
+ * Collapse an array of resource contents to a single text string when every
354
+ * part carries text. Returns `undefined` if any part is binary (blob) or has
355
+ * no text, signaling the caller that a token-efficient summary isn't
356
+ * available — in which case the full `data` array still carries everything.
357
+ */
358
+ function flattenResourceText(contents) {
359
+ if (contents.length === 0)
360
+ return undefined;
361
+ const parts = [];
362
+ for (const part of contents) {
363
+ if (typeof part.text === 'string') {
364
+ parts.push(part.text);
365
+ }
366
+ else {
367
+ return undefined;
368
+ }
369
+ }
370
+ return parts.join('\n\n');
371
+ }
372
+ /**
373
+ * Collapse a prompt's message array to a single text summary in
374
+ * `<role>: <text>` format. Returns `undefined` when any message has a
375
+ * non-text content shape (image, resource reference, …).
376
+ */
377
+ function flattenPromptMessages(messages) {
378
+ if (messages.length === 0)
379
+ return undefined;
380
+ const lines = [];
381
+ for (const msg of messages) {
382
+ const text = extractMessageText(msg.content);
383
+ if (text === undefined)
384
+ return undefined;
385
+ lines.push(`${msg.role}: ${text}`);
386
+ }
387
+ return lines.join('\n\n');
388
+ }
389
+ function extractMessageText(content) {
390
+ if (typeof content === 'string')
391
+ return content;
392
+ if (content && typeof content === 'object') {
393
+ const c = content;
394
+ if (c.type === 'text' && typeof c.text === 'string')
395
+ return c.text;
396
+ }
397
+ return undefined;
398
+ }
399
+ /**
400
+ * Extract a human-readable error string from an MCP `CallToolResult` that
401
+ * came back with `isError: true`. Servers put the error detail in `content`
402
+ * per spec; we concatenate any text parts.
403
+ */
404
+ function extractErrorText(result) {
405
+ const texts = [];
406
+ for (const part of result.content) {
407
+ if (part && typeof part === 'object') {
408
+ const p = part;
409
+ if (p.type === 'text' && typeof p.text === 'string') {
410
+ texts.push(p.text);
411
+ }
412
+ }
413
+ }
414
+ return texts.length > 0 ? texts.join('\n') : 'Tool reported error (no detail)';
415
+ }
7
416
  /**
8
417
  * Create a Tool from an MCP tool definition
9
418
  */
@@ -108,6 +517,30 @@ export function discoverMCPTools(source) {
108
517
  return agentTool;
109
518
  });
110
519
  }
520
+ /**
521
+ * Narrow an untyped `inputSchema` from a spec `Tool` into the shape
522
+ * `jsonSchemaToZod` understands, then delegate. Falls back to a permissive
523
+ * `z.object({})` when the input is malformed so tool discovery never hard-fails
524
+ * on a misbehaving server.
525
+ */
526
+ function jsonSchemaObjectToZod(raw) {
527
+ if (!(raw && typeof raw === 'object'))
528
+ return z.object({});
529
+ const s = raw;
530
+ if (s.type !== 'object')
531
+ return z.object({});
532
+ const properties = s.properties && typeof s.properties === 'object'
533
+ ? s.properties
534
+ : undefined;
535
+ const required = Array.isArray(s.required)
536
+ ? s.required.filter((r) => typeof r === 'string')
537
+ : undefined;
538
+ return jsonSchemaToZod({
539
+ type: 'object',
540
+ ...(properties !== undefined ? { properties } : {}),
541
+ ...(required !== undefined ? { required } : {}),
542
+ });
543
+ }
111
544
  /**
112
545
  * Convert JSON Schema to Zod schema (simplified version)
113
546
  * For production, use a proper library like json-schema-to-zod
@@ -0,0 +1,128 @@
1
+ /**
2
+ * MCP Elicitation — route server `elicitation/create` requests through
3
+ * a consumer-provided UI callback (Stage 5.3 of the MCP v1 plan).
4
+ *
5
+ * The MCP spec defines `elicitation/create` as a server-to-client
6
+ * request: a server asks the client to collect structured input from
7
+ * the user mid-flow (form inputs, confirmations, auth prompts). The
8
+ * agent runtime is NOT the right place to decide what UI to show —
9
+ * each consumer (admin inspector, agent execution panel, CLI tool,
10
+ * Slack bot, …) renders in its own idiom. This module exposes
11
+ * `createElicitationHandler({ onElicit })` — a thin factory that
12
+ * wraps the consumer's async UI callback into the structural handler
13
+ * shape that `McpClient` from `@revealui/mcp/client` expects.
14
+ *
15
+ * Safety defaults
16
+ * ---------------
17
+ * - Servers that request **URL mode** (out-of-band consent via a
18
+ * separate browser tab) are auto-declined unless `allowUrlMode: true`
19
+ * is passed. URL mode is a phishing vector when the user can't
20
+ * easily verify what domain they're on — admin inspector (Stage 3.4)
21
+ * takes the same posture; agent runtime matches for consistency.
22
+ * - Optional `timeoutMs` auto-declines after the deadline. Useful in
23
+ * headless automation where no human is at the keyboard.
24
+ * - Thrown errors inside `onElicit` are converted to `{ action:
25
+ * 'cancel' }` rather than propagating — servers should see a clean
26
+ * decline, not a protocol error the UI crashed on.
27
+ *
28
+ * As with the rest of the MCP-adapter surface, this module uses
29
+ * structural typing to stay decoupled from `@revealui/mcp` — the real
30
+ * `ElicitationHandler` from `@revealui/mcp/client` structurally
31
+ * satisfies the `McpElicitationHandler` shape exported here.
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * import { McpClient } from '@revealui/mcp/client';
36
+ * import { createElicitationHandler } from '@revealui/ai';
37
+ *
38
+ * const client = new McpClient({
39
+ * clientInfo: { name: 'my-agent', version: '1.0.0' },
40
+ * transport: { kind: 'streamable-http', url: '…' },
41
+ * elicitationHandler: createElicitationHandler({
42
+ * onElicit: async ({ message, requestedSchema }) => {
43
+ * const form = await showFormDialog({ title: message, schema: requestedSchema });
44
+ * if (!form) return { action: 'cancel' };
45
+ * return { action: 'accept', content: form.values };
46
+ * },
47
+ * timeoutMs: 60_000,
48
+ * }),
49
+ * });
50
+ * ```
51
+ */
52
+ import { type McpEventSink } from './mcp-events.js';
53
+ /** Parameters of an `elicitation/create` request (spec-shaped subset). */
54
+ export interface McpElicitRequestParams {
55
+ /**
56
+ * Elicitation mode. Currently the spec defines `'form'` (inline form
57
+ * fields) as the only named value; future modes (e.g. `'url'` for
58
+ * out-of-band consent) may appear. Undefined = `'form'`.
59
+ */
60
+ mode?: string;
61
+ /** User-facing prompt message. */
62
+ message: string;
63
+ /** JSON Schema describing the fields to collect. */
64
+ requestedSchema: {
65
+ type: 'object';
66
+ properties: Record<string, unknown>;
67
+ required?: ReadonlyArray<string>;
68
+ };
69
+ }
70
+ /** Allowed values for `ElicitResult.content` per the MCP spec. */
71
+ export type McpElicitContentValue = string | number | boolean | ReadonlyArray<string>;
72
+ /** Result of an `elicitation/create` request (spec-shaped subset). */
73
+ export interface McpElicitResult {
74
+ action: 'accept' | 'decline' | 'cancel';
75
+ content?: Record<string, McpElicitContentValue>;
76
+ }
77
+ /** Structural shape of the handler — matches `ElicitationHandler` from `@revealui/mcp/client`. */
78
+ export type McpElicitationHandler = (params: McpElicitRequestParams) => Promise<McpElicitResult>;
79
+ export interface CreateElicitationHandlerOptions {
80
+ /**
81
+ * Consumer-supplied async callback that renders UI and collects the
82
+ * response. Receives the spec-shaped request params; returns a
83
+ * spec-shaped result. The factory handles URL-mode auto-decline,
84
+ * timeouts, and error-to-cancel mapping around this callback.
85
+ */
86
+ onElicit: (params: McpElicitRequestParams) => Promise<McpElicitResult>;
87
+ /**
88
+ * Auto-decline deadline in milliseconds. When set and no response
89
+ * arrives within the window, the handler returns `{ action: 'cancel' }`
90
+ * (not `'decline'` — the timeout wasn't a user decision). Set to
91
+ * `undefined` / omit to wait indefinitely.
92
+ */
93
+ timeoutMs?: number;
94
+ /**
95
+ * Accept `mode: 'url'` elicitation requests (out-of-band consent via
96
+ * a separate browser tab). Default `false` — URL-mode requests are
97
+ * auto-declined for safety. Flip to `true` only if the consumer's
98
+ * UI renders a verifiable domain-check + explicit user approval.
99
+ */
100
+ allowUrlMode?: boolean;
101
+ /**
102
+ * Observability hook. Fires before `onElicit` is invoked with the
103
+ * message + field count. Useful for audit trails.
104
+ */
105
+ onElicitationRequest?: (info: {
106
+ message: string;
107
+ fieldCount: number;
108
+ mode?: string;
109
+ }) => void;
110
+ /**
111
+ * Protocol-level observability sink (Stage 6.1). Fires once per
112
+ * `elicitation/create` call (after the handler decides — whether
113
+ * the user accepted, declined, cancelled, url-mode auto-declined,
114
+ * or the timeout fired) with `{ kind: 'mcp.elicitation.create',
115
+ * action, fieldCount, mode?, duration_ms, success }`. URL-mode
116
+ * auto-decline + timeout-cancel both emit `success: true` — they
117
+ * are legitimate handler outcomes, not failures. Only thrown errors
118
+ * report `success: false`.
119
+ */
120
+ onEvent?: McpEventSink;
121
+ /**
122
+ * Optional server identifier included in emitted events. Leave unset
123
+ * when the handler is shared across multiple servers.
124
+ */
125
+ namespace?: string;
126
+ }
127
+ export declare function createElicitationHandler(options: CreateElicitationHandlerOptions): McpElicitationHandler;
128
+ //# sourceMappingURL=mcp-elicitation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-elicitation.d.ts","sourceRoot":"","sources":["../../src/tools/mcp-elicitation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AAGH,OAAO,EAAgB,KAAK,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAMlE,0EAA0E;AAC1E,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,oDAAoD;IACpD,eAAe,EAAE;QACf,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;KAClC,CAAC;CACH;AAED,kEAAkE;AAClE,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;AAEtF,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,QAAQ,GAAG,SAAS,GAAG,QAAQ,CAAC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,qBAAqB,CAAC,CAAC;CACjD;AAED,kGAAkG;AAClG,MAAM,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,sBAAsB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAMjG,MAAM,WAAW,+BAA+B;IAC9C;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,sBAAsB,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IACvE;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9F;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,YAAY,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,qBAAqB,CAiEvB"}