@revealui/ai 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 (69) 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/llm/client.d.ts +33 -4
  14. package/dist/llm/client.d.ts.map +1 -1
  15. package/dist/llm/client.js +70 -21
  16. package/dist/llm/key-validator.d.ts.map +1 -1
  17. package/dist/llm/key-validator.js +0 -27
  18. package/dist/llm/providers/inference-snaps.d.ts +7 -5
  19. package/dist/llm/providers/inference-snaps.d.ts.map +1 -1
  20. package/dist/llm/providers/inference-snaps.js +7 -5
  21. package/dist/llm/providers/openai-compat.d.ts +1 -1
  22. package/dist/llm/providers/openai-compat.js +1 -1
  23. package/dist/llm/response-cache.d.ts +1 -2
  24. package/dist/llm/response-cache.d.ts.map +1 -1
  25. package/dist/llm/response-cache.js +1 -2
  26. package/dist/llm/server.d.ts +1 -1
  27. package/dist/llm/server.d.ts.map +1 -1
  28. package/dist/llm/server.js +3 -1
  29. package/dist/memory/services/index.d.ts +2 -0
  30. package/dist/memory/services/index.d.ts.map +1 -1
  31. package/dist/memory/services/index.js +1 -0
  32. package/dist/memory/services/reconciliation-service.d.ts +64 -0
  33. package/dist/memory/services/reconciliation-service.d.ts.map +1 -0
  34. package/dist/memory/services/reconciliation-service.js +127 -0
  35. package/dist/memory/utils/sql-helpers.d.ts +19 -25
  36. package/dist/memory/utils/sql-helpers.d.ts.map +1 -1
  37. package/dist/memory/utils/sql-helpers.js +34 -81
  38. package/dist/memory/vector/vector-memory-service.d.ts.map +1 -1
  39. package/dist/memory/vector/vector-memory-service.js +6 -1
  40. package/dist/orchestration/runtime.d.ts +30 -1
  41. package/dist/orchestration/runtime.d.ts.map +1 -1
  42. package/dist/orchestration/runtime.js +20 -3
  43. package/dist/orchestration/streaming-runtime.d.ts +48 -1
  44. package/dist/orchestration/streaming-runtime.d.ts.map +1 -1
  45. package/dist/orchestration/ticket-agent.d.ts +10 -1
  46. package/dist/orchestration/ticket-agent.d.ts.map +1 -1
  47. package/dist/orchestration/ticket-agent.js +11 -2
  48. package/dist/tools/mcp-adapter.d.ts +192 -1
  49. package/dist/tools/mcp-adapter.d.ts.map +1 -1
  50. package/dist/tools/mcp-adapter.js +434 -1
  51. package/dist/tools/mcp-elicitation.d.ts +128 -0
  52. package/dist/tools/mcp-elicitation.d.ts.map +1 -0
  53. package/dist/tools/mcp-elicitation.js +121 -0
  54. package/dist/tools/mcp-events.d.ts +270 -0
  55. package/dist/tools/mcp-events.d.ts.map +1 -0
  56. package/dist/tools/mcp-events.js +165 -0
  57. package/dist/tools/mcp-sampling.d.ts +155 -0
  58. package/dist/tools/mcp-sampling.d.ts.map +1 -0
  59. package/dist/tools/mcp-sampling.js +171 -0
  60. package/dist/tools/ticket-tools.d.ts +35 -2
  61. package/dist/tools/ticket-tools.d.ts.map +1 -1
  62. package/dist/tools/ticket-tools.js +32 -2
  63. package/package.json +13 -11
  64. package/dist/llm/providers/vultr.d.ts +0 -18
  65. package/dist/llm/providers/vultr.d.ts.map +0 -1
  66. package/dist/llm/providers/vultr.js +0 -169
  67. package/dist/memory/utils/deep-clone.d.ts +0 -10
  68. package/dist/memory/utils/deep-clone.d.ts.map +0 -1
  69. package/dist/memory/utils/deep-clone.js +0 -9
@@ -1,9 +1,27 @@
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 type { Tool } from './base.js';
24
+ import { type McpEventSink } from './mcp-events.js';
7
25
  export interface MCPTool {
8
26
  name: string;
9
27
  description: string;
@@ -20,6 +38,10 @@ export interface MCPClient {
20
38
  /**
21
39
  * Interface for an MCP tool source (e.g. MCPHypervisor from @revealui/mcp).
22
40
  * Using an interface here keeps @revealui/ai decoupled from @revealui/mcp.
41
+ *
42
+ * @deprecated Prefer `McpClientLike` + `createToolsFromMcpClient()` (Stage 5.1a).
43
+ * The hypervisor path doesn't expose the full MCP protocol surface (resources,
44
+ * prompts, sampling, elicitation). The typed-client path does.
23
45
  */
24
46
  export interface MCPToolSource {
25
47
  getAllTools(): Array<{
@@ -29,6 +51,175 @@ export interface MCPToolSource {
29
51
  }>;
30
52
  callTool(serverName: string, toolName: string, args: unknown): Promise<unknown>;
31
53
  }
54
+ /**
55
+ * Structural shape of an `McpClient` from `@revealui/mcp/client`. Declared
56
+ * here (rather than imported) to keep `@revealui/ai` decoupled from
57
+ * `@revealui/mcp` at the type level. Consumers pass any object whose
58
+ * `listTools()` + `callTool()` methods match — the real `McpClient`
59
+ * structurally satisfies this shape.
60
+ *
61
+ * Returns / options are loose-typed to the spec-relevant bits; the real
62
+ * `McpClient` returns richer SDK types which we don't need for tool dispatch.
63
+ */
64
+ export interface McpClientLike {
65
+ listTools(options?: unknown): Promise<ReadonlyArray<McpToolDescriptor>>;
66
+ callTool(name: string, args?: Record<string, unknown>, options?: unknown): Promise<McpCallToolResultLike>;
67
+ /**
68
+ * Resources (Stage 5.1b). Optional: when absent on the client, the
69
+ * adapter skips emitting resource meta-tools for the server regardless
70
+ * of `include.resources`.
71
+ */
72
+ listResources?(options?: unknown): Promise<ReadonlyArray<McpResourceDescriptor>>;
73
+ readResource?(uri: string, options?: unknown): Promise<ReadonlyArray<McpResourceContentLike>>;
74
+ /**
75
+ * Prompts (Stage 5.1b). Optional: when absent, prompt meta-tools are
76
+ * skipped for the server.
77
+ */
78
+ listPrompts?(options?: unknown): Promise<ReadonlyArray<McpPromptDescriptor>>;
79
+ getPrompt?(name: string, args?: Record<string, string>, options?: unknown): Promise<McpGetPromptResultLike>;
80
+ }
81
+ /** Subset of the spec `Tool` shape needed for agent-side discovery. */
82
+ export interface McpToolDescriptor {
83
+ name: string;
84
+ description?: string;
85
+ inputSchema: unknown;
86
+ }
87
+ /** Subset of the spec `CallToolResult` shape. */
88
+ export interface McpCallToolResultLike {
89
+ content: ReadonlyArray<unknown>;
90
+ isError?: boolean;
91
+ structuredContent?: unknown;
92
+ }
93
+ /** Subset of the spec `Resource` shape needed for listing. */
94
+ export interface McpResourceDescriptor {
95
+ uri: string;
96
+ name?: string;
97
+ description?: string;
98
+ mimeType?: string;
99
+ }
100
+ /** One element of `readResource`'s response — either a text or a blob content part. */
101
+ export interface McpResourceContentLike {
102
+ uri: string;
103
+ mimeType?: string;
104
+ text?: string;
105
+ blob?: string;
106
+ }
107
+ /** Subset of the spec `Prompt` shape needed for listing. */
108
+ export interface McpPromptDescriptor {
109
+ name: string;
110
+ description?: string;
111
+ arguments?: ReadonlyArray<{
112
+ name: string;
113
+ description?: string;
114
+ required?: boolean;
115
+ }>;
116
+ }
117
+ /** Subset of the spec `GetPromptResult` shape. */
118
+ export interface McpGetPromptResultLike {
119
+ description?: string;
120
+ messages: ReadonlyArray<{
121
+ role: 'user' | 'assistant' | string;
122
+ content: unknown;
123
+ }>;
124
+ }
125
+ export interface CreateToolsFromMcpClientOptions {
126
+ /**
127
+ * Namespace (typically the server's identifier) prepended to each tool
128
+ * name so collisions across multiple MCP clients are impossible.
129
+ * Output tool name: `mcp_<namespace>__<toolName>`.
130
+ */
131
+ namespace: string;
132
+ /**
133
+ * Optional category tag used for tool-level telemetry + UI grouping.
134
+ * Defaults to `'mcp'`.
135
+ */
136
+ category?: string;
137
+ /**
138
+ * Which MCP primitives to expose to the agent as tools (Stage 5.1b).
139
+ *
140
+ * - `tools` (default `true`): wrap every server tool.
141
+ * - `resources` (default `true`): emit two meta-tools per namespace —
142
+ * `mcp_<ns>__list_resources` and `mcp_<ns>__read_resource({ uri })`.
143
+ * Skipped silently when the client doesn't expose `listResources` /
144
+ * `readResource`.
145
+ * - `prompts` (default `true`): emit two meta-tools per namespace —
146
+ * `mcp_<ns>__list_prompts` and `mcp_<ns>__get_prompt({ name, args? })`.
147
+ * Skipped silently when the client doesn't expose `listPrompts` /
148
+ * `getPrompt`.
149
+ */
150
+ include?: {
151
+ tools?: boolean;
152
+ resources?: boolean;
153
+ prompts?: boolean;
154
+ };
155
+ /**
156
+ * Progress observability hook (Stage 5.3). Every server tool call made
157
+ * through the wrapped `Tool.execute()` forwards per-request progress
158
+ * notifications here. Resource + prompt meta-tools don't emit
159
+ * progress (they're single-shot lookups).
160
+ */
161
+ onProgress?: (event: McpProgressEvent) => void;
162
+ /**
163
+ * Optional `AbortSignal` forwarded to every server tool call (Stage
164
+ * 5.3). When the signal aborts, in-flight MCP RPC calls are
165
+ * cancelled via `notifications/cancelled` per the MCP spec. Useful
166
+ * for wiring a consumer-facing cancel button into an active
167
+ * agent run. Resource/prompt meta-tools also receive the signal.
168
+ */
169
+ signal?: AbortSignal;
170
+ /**
171
+ * Protocol-level observability sink (Stage 6.1). Fires exactly once
172
+ * per tool / resource / prompt call with a structured summary
173
+ * (`namespace`, operation, `duration_ms`, `success`, `error?`). See
174
+ * `./mcp-events.ts` for the event shapes and `createCoreLoggerSink()`
175
+ * for the default routing to `@revealui/core/observability/logger`.
176
+ * Safe to pass a throwing sink — adapter swallows sink errors.
177
+ */
178
+ onEvent?: McpEventSink;
179
+ }
180
+ /** Spec-shaped `Progress` notification subset. */
181
+ export interface McpProgressLike {
182
+ /** Monotonically increasing progress token value. */
183
+ progress: number;
184
+ /** Total expected units of work, when the server knows it. */
185
+ total?: number;
186
+ /** Human-readable status message. */
187
+ message?: string;
188
+ }
189
+ /** Event payload forwarded to `onProgress`. */
190
+ export interface McpProgressEvent {
191
+ /** Namespace the tool belongs to (for multi-client agents). */
192
+ namespace: string;
193
+ /** Wrapped tool name that emitted the event (without the `mcp_<ns>__` prefix). */
194
+ toolName: string;
195
+ /** Progress payload as reported by the server. */
196
+ progress: McpProgressLike;
197
+ }
198
+ /**
199
+ * Connect the agent's tool registry to a standard MCP client. Lists tools
200
+ * from the MCP server and returns agent-side `Tool` instances that dispatch
201
+ * calls back through the client. Safe to call multiple times per server —
202
+ * each invocation re-reads the current tool list.
203
+ *
204
+ * @example
205
+ * ```typescript
206
+ * import { McpClient } from '@revealui/mcp/client';
207
+ * import { createToolsFromMcpClient } from '@revealui/ai';
208
+ *
209
+ * const client = new McpClient({
210
+ * clientInfo: { name: 'my-agent', version: '1.0.0' },
211
+ * transport: { kind: 'streamable-http', url: 'https://example.com/mcp' },
212
+ * });
213
+ * await client.connect();
214
+ *
215
+ * const tools = await createToolsFromMcpClient(client, {
216
+ * namespace: 'example-server',
217
+ * });
218
+ *
219
+ * agent.tools.push(...tools);
220
+ * ```
221
+ */
222
+ export declare function createToolsFromMcpClient(client: McpClientLike, options: CreateToolsFromMcpClientOptions): Promise<Tool[]>;
32
223
  /**
33
224
  * Create a Tool from an MCP tool definition
34
225
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mcp-adapter.d.ts","sourceRoot":"","sources":["../../src/tools/mcp-adapter.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,WAAW,CAAC;AAElD,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAC;QACf,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACpC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;KACrB,CAAC;CACH;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzD;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,WAAW,IAAI,KAAK,CAAC;QAAE,cAAc,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IACpF,QAAQ,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACjF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,IAAI,CAuB9E;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,SAAS,EACpB,QAAQ,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;CAAE,GACvC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAa1D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,EAAE,CAkC9D"}
1
+ {"version":3,"file":"mcp-adapter.d.ts","sourceRoot":"","sources":["../../src/tools/mcp-adapter.ts"],"names":[],"mappings":"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"}
@@ -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