@epam/ai-dial-mcp-apps 1.1.0-dev.405

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.
@@ -0,0 +1,25 @@
1
+ import { McpAppHostAdapter, McpAppResponseCache, McpAppToolCallSeed, McpAppToolRef } from '../../models/mcp-apps';
2
+ /** Props for the `McpAppInlinePreview` component. */
3
+ export interface McpAppInlinePreviewProps {
4
+ /** Matched MCP App tool to preview. */
5
+ match: McpAppToolRef;
6
+ /** Original tool call's arguments/result, seeding the mounted app's initial state. */
7
+ toolCall?: McpAppToolCallSeed;
8
+ /** Shared cache (with a full-width canvas) so switching to it for this message reuses this preview's fetch instead of repeating it. */
9
+ cache: McpAppResponseCache;
10
+ /** This message's cache key (e.g. `mcpAppCanvasKey(index)`). */
11
+ cacheKey: string;
12
+ /** Host-supplied dependencies — display context, sandbox URL, and the resource-fetch/tool-call functions. */
13
+ hostAdapter: McpAppHostAdapter;
14
+ /** Called when the user activates the expand-to-canvas button. */
15
+ onExpand: () => void;
16
+ /** Accessible label for the expand-to-canvas button. */
17
+ expandAriaLabel: string;
18
+ /** Accessible label for the reload button. */
19
+ reloadAriaLabel: string;
20
+ /** Message shown when the resource fails to load or the app fails to initialize. */
21
+ loadErrorLabel: string;
22
+ }
23
+ /** Renders a compact, always-visible preview of a message's matched MCP App — see `McpAppInlinePreviewProps`. */
24
+ export declare const McpAppInlinePreview: import('react').NamedExoticComponent<McpAppInlinePreviewProps>;
25
+ //# sourceMappingURL=McpAppInlinePreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"McpAppInlinePreview.d.ts","sourceRoot":"","sources":["../../../src/components/McpAppInlinePreview/McpAppInlinePreview.tsx"],"names":[],"mappings":"AAaA,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAE/B,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACvC,uCAAuC;IACvC,KAAK,EAAE,aAAa,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,uIAAuI;IACvI,KAAK,EAAE,mBAAmB,CAAC;IAC3B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,6GAA6G;IAC7G,WAAW,EAAE,iBAAiB,CAAC;IAC/B,kEAAkE;IAClE,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,wDAAwD;IACxD,eAAe,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,eAAe,EAAE,MAAM,CAAC;IACxB,oFAAoF;IACpF,cAAc,EAAE,MAAM,CAAC;CACxB;AAmFD,iHAAiH;AACjH,eAAO,MAAM,mBAAmB,gEAAgC,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { McpAppCanvasContent } from '@epam/ai-dial-attachment-canvas';
2
+ import { McpAppInlinePreviewStatus, McpAppHostAdapter, McpAppResponseCache, McpAppToolCallSeed, McpAppToolRef } from '../../models/mcp-apps';
3
+ /** State returned by `useMcpAppInlinePreview`. */
4
+ export interface McpAppInlinePreviewState {
5
+ /** Current load state of the preview's fetch. */
6
+ status: McpAppInlinePreviewStatus;
7
+ /** Present only when `status` is `Ready`. */
8
+ content?: McpAppCanvasContent;
9
+ /** Re-fetches the resource and re-resolves the tool result from scratch, bypassing `cache`. */
10
+ reload: () => void;
11
+ }
12
+ /**
13
+ * Fetches an MCP App's `ui://` resource and resolves its seeded tool result
14
+ * for a compact, inline preview embedded directly under a message — a
15
+ * lighter-weight sibling of a full-width canvas that mounts independently of
16
+ * any canvas/side-panel machinery, so it can render alongside the message
17
+ * body instead of taking over the canvas. Builds `content.hostContext` from
18
+ * `hostAdapter.hostContext` so the app can render a layout appropriate to
19
+ * where it's mounted.
20
+ *
21
+ * `cache` (shared with a full-width canvas via the same
22
+ * `useMcpAppResponseCache` instance, keyed by `cacheKey`) is checked before
23
+ * fetching — switching from this preview to the full canvas (or back) for
24
+ * the same message reuses the same fetch/live-tool-re-call instead of
25
+ * repeating it. Call the returned `reload` to bypass and refresh it.
26
+ */
27
+ export declare const useMcpAppInlinePreview: (match: McpAppToolRef | undefined, toolCall: McpAppToolCallSeed | undefined, cache: McpAppResponseCache, cacheKey: string, hostAdapter: McpAppHostAdapter) => McpAppInlinePreviewState;
28
+ //# sourceMappingURL=useMcpAppInlinePreview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMcpAppInlinePreview.d.ts","sourceRoot":"","sources":["../../../src/hooks/useMcpAppInlinePreview/useMcpAppInlinePreview.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,mBAAmB,EACzB,MAAM,iCAAiC,CAAC;AAGzC,OAAO,EACL,yBAAyB,EACzB,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAC;AAM/B,kDAAkD;AAClD,MAAM,WAAW,wBAAwB;IACvC,iDAAiD;IACjD,MAAM,EAAE,yBAAyB,CAAC;IAClC,6CAA6C;IAC7C,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,+FAA+F;IAC/F,MAAM,EAAE,MAAM,IAAI,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,aAAa,GAAG,SAAS,EAChC,UAAU,kBAAkB,GAAG,SAAS,EACxC,OAAO,mBAAmB,EAC1B,UAAU,MAAM,EAChB,aAAa,iBAAiB,KAC7B,wBAgGF,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { McpAppResponseCache } from '../../models/mcp-apps';
2
+ /**
3
+ * In-memory cache of a fetched MCP App resource + its resolved tool result,
4
+ * keyed by a caller-supplied `key` (e.g. `mcpAppCanvasKey(messageIndex)`),
5
+ * scoped to one open conversation. Lets an inline preview and a full-width
6
+ * canvas reuse the same fetch/live-tool-re-call for the same message instead
7
+ * of repeating it every time the user switches between the two.
8
+ *
9
+ * Each entry is tagged with the `seedKey` (`computeMcpAppSeedKey`) it was
10
+ * resolved from and a 15-minute TTL: a freshly-streamed message mounts the
11
+ * preview before the tool call is known, so the first write happens with an
12
+ * undefined seed. `get` treats a later, settled seed as a miss rather than
13
+ * reusing that earlier, seedless entry.
14
+ *
15
+ * The underlying `Map`s (entries, and `getOrFetch`'s in-flight promises)
16
+ * live in `useRef`s, mutated only inside `get`/`set`/`invalidate`/`getOrFetch`
17
+ * — functions called later by consumers (in an effect or event handler),
18
+ * never during this hook's own render — so no ref is read or written while
19
+ * rendering. Entries are namespaced by `conversationId`
20
+ * (`${conversationId}:${key}`) rather than clearing the map on conversation
21
+ * switch, since clearing would itself require touching the ref during
22
+ * render; `set` opportunistically prunes expired entries instead, keeping
23
+ * the map bounded without a per-conversation reset.
24
+ */
25
+ export declare const useMcpAppResponseCache: (conversationId: string) => McpAppResponseCache;
26
+ //# sourceMappingURL=useMcpAppResponseCache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMcpAppResponseCache.d.ts","sourceRoot":"","sources":["../../../src/hooks/useMcpAppResponseCache/useMcpAppResponseCache.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,mBAAmB,EACpB,MAAM,uBAAuB,CAAC;AAW/B;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,sBAAsB,GACjC,gBAAgB,MAAM,KACrB,mBAyEF,CAAC"}
package/index.d.ts ADDED
@@ -0,0 +1,7 @@
1
+ export { McpAppInlinePreview, type McpAppInlinePreviewProps, } from './components/McpAppInlinePreview/McpAppInlinePreview';
2
+ export { useMcpAppInlinePreview, type McpAppInlinePreviewState, } from './hooks/useMcpAppInlinePreview/useMcpAppInlinePreview';
3
+ export { useMcpAppResponseCache } from './hooks/useMcpAppResponseCache/useMcpAppResponseCache';
4
+ export type { McpDeploymentKind, McpAppToolDiscovery, McpAppToolRef, McpAppToolCallSeed, FetchMcpAppResourceHtml, CallMcpAppTool, CachedMcpAppResponse, McpAppResponseCache, McpAppHostAdapter, } from './models/mcp-apps';
5
+ export { McpAppInlinePreviewStatus } from './models/mcp-apps';
6
+ export { collectToolCallNames, findMcpAppForMessage, mcpAppCanvasKey, computeMcpAppSeedKey, resolveMcpAppToolCallSeed, resolveMcpAppToolResult, } from './utils/mcp-app';
7
+ //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,mBAAmB,EACnB,KAAK,wBAAwB,GAC9B,MAAM,sDAAsD,CAAC;AAC9D,OAAO,EACL,sBAAsB,EACtB,KAAK,wBAAwB,GAC9B,MAAM,uDAAuD,CAAC;AAC/D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uDAAuD,CAAC;AAC/F,YAAY,EACV,iBAAiB,EACjB,mBAAmB,EACnB,aAAa,EACb,kBAAkB,EAClB,uBAAuB,EACvB,cAAc,EACd,oBAAoB,EACpB,mBAAmB,EACnB,iBAAiB,GAClB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAC9D,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,eAAe,EACf,oBAAoB,EACpB,yBAAyB,EACzB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC"}
package/index.js ADDED
@@ -0,0 +1,216 @@
1
+ import { AttachmentContentType as e, McpAppCanvasRenderer as t } from "@epam/ai-dial-attachment-canvas";
2
+ import { DIAL_KIT_ICON_STROKE as n, GhostIconButton as r, Spinner as i } from "@epam/ai-dial-ui-kit";
3
+ import { IconAlertTriangle as a, IconArrowsMaximize as o, IconRefresh as s } from "@tabler/icons-react";
4
+ import { memo as c, useCallback as l, useEffect as u, useMemo as d, useRef as f, useState as p } from "react";
5
+ import { MessageRole as m } from "@epam/ai-dial-chat-shared";
6
+ import { jsx as h, jsxs as g } from "react/jsx-runtime";
7
+ //#region src/models/mcp-apps.ts
8
+ var _ = /* @__PURE__ */ function(e) {
9
+ return e.Loading = "loading", e.Ready = "ready", e.Error = "error", e.Unavailable = "unavailable", e;
10
+ }({}), v = (e) => {
11
+ try {
12
+ let t = JSON.parse(e);
13
+ return typeof t == "object" && t ? t : {};
14
+ } catch {
15
+ return {};
16
+ }
17
+ }, y = (e) => {
18
+ let t = /* @__PURE__ */ new Map();
19
+ for (let n of e?.tool_messages ?? []) {
20
+ for (let e of n.tool_calls ?? []) t.set(e.id, {
21
+ name: e.name,
22
+ args: e.args
23
+ });
24
+ if (n.type === "tool" && n.tool_call_id) {
25
+ let e = t.get(n.tool_call_id);
26
+ e && (e.result = n.content);
27
+ }
28
+ }
29
+ for (let n of e?.tool_execution_history ?? []) {
30
+ for (let e of n.tool_calls ?? []) t.set(e.id, {
31
+ name: e.function.name,
32
+ args: v(e.function.arguments)
33
+ });
34
+ if (n.role === "tool" && n.tool_call_id) {
35
+ let e = t.get(n.tool_call_id);
36
+ e && (e.result = n.content);
37
+ }
38
+ }
39
+ return t;
40
+ }, b = (e) => {
41
+ let t = /* @__PURE__ */ new Set();
42
+ for (let n of e) for (let e of y(n.custom_content?.state).values()) t.add(e.name);
43
+ return t;
44
+ }, x = (e, t, n) => {
45
+ if (t.length === 0 || e.role !== m.Assistant) return;
46
+ let r = new Set([...y(e.custom_content?.state).values()].map((e) => e.name));
47
+ return t.find((e) => r.has(e.toolName)) || t.find((e) => e.discovery === "direct") || (n ? t[0] : void 0);
48
+ }, S = (e) => `${e}:mcp-app`, C = (e) => e == null ? void 0 : JSON.stringify(e.toolInput ?? null), w = (e, t) => {
49
+ let n = e.custom_content?.state;
50
+ if (!n) return;
51
+ let r = [...y(n).values()].reverse().find((e) => e.name === t);
52
+ if (r) return {
53
+ toolInput: r.args,
54
+ toolResult: r.result == null ? void 0 : { content: [{
55
+ type: "text",
56
+ text: r.result
57
+ }] }
58
+ };
59
+ }, T = async (e, t, n) => {
60
+ if (!(e.discovery === "direct" || t?.toolInput != null)) return t?.toolResult;
61
+ try {
62
+ return await n(e.toolsetId, e.mcpToolName, t?.toolInput ?? {}, e.kind);
63
+ } catch {
64
+ return t?.toolResult;
65
+ }
66
+ }, E = (t, n, r, i, a) => {
67
+ let { hostContext: o, sandboxUrl: s, fetchResourceHtml: c, callTool: d } = a, [f, m] = p(), [h, g] = p(), [v, y] = p(() => t == null || s == null ? _.Unavailable : _.Loading), [b, x] = p(0), S = l(() => {
68
+ r.invalidate(i), x((e) => e + 1);
69
+ }, [r, i]), w = l(async (e) => {
70
+ if (!(t == null || s == null)) try {
71
+ let a = C(n), { html: o, toolResult: s } = await r.getOrFetch(i, a, async () => {
72
+ let [e, r] = await Promise.all([c(t.toolsetId, t.resourceUri), T(t, n, d)]);
73
+ return {
74
+ html: e,
75
+ toolResult: r
76
+ };
77
+ });
78
+ if (e()) return;
79
+ m(o), g(s), y(_.Ready);
80
+ } catch {
81
+ e() || y(_.Error);
82
+ }
83
+ }, [
84
+ t,
85
+ n,
86
+ s,
87
+ r,
88
+ i,
89
+ c,
90
+ d
91
+ ]);
92
+ u(() => {
93
+ if (t == null || s == null) {
94
+ y(_.Unavailable);
95
+ return;
96
+ }
97
+ let e = !1;
98
+ return y(_.Loading), w(() => e), () => {
99
+ e = !0;
100
+ };
101
+ }, [
102
+ t,
103
+ s,
104
+ w,
105
+ b
106
+ ]);
107
+ let E = l(async (e, n) => {
108
+ if (t == null) throw Error(`Tool call "${e}" failed: no matching MCP App`);
109
+ return d(t.toolsetId, e, n, t.kind);
110
+ }, [t, d]);
111
+ return v !== _.Ready || t == null || s == null || f == null ? {
112
+ status: v,
113
+ reload: S
114
+ } : {
115
+ status: v,
116
+ reload: S,
117
+ content: {
118
+ type: e.McpApp,
119
+ html: f,
120
+ sandboxUrl: s,
121
+ toolName: t.mcpToolName,
122
+ toolInput: n?.toolInput,
123
+ toolResult: h,
124
+ hostContext: o,
125
+ onToolCall: E
126
+ }
127
+ };
128
+ }, D = c(({ match: e, toolCall: c, cache: l, cacheKey: u, hostAdapter: d, onExpand: f, expandAriaLabel: p, reloadAriaLabel: m, loadErrorLabel: v }) => {
129
+ let { status: y, content: b, reload: x } = E(e, c, l, u, d);
130
+ return y === _.Unavailable ? null : /* @__PURE__ */ g("div", {
131
+ className: "bg-layer-2 flex w-full flex-col overflow-hidden rounded border border-tertiary",
132
+ children: [/* @__PURE__ */ g("div", {
133
+ className: "flex items-center justify-end gap-1 border-b border-tertiary px-3 py-1.5",
134
+ children: [/* @__PURE__ */ h(r, {
135
+ icon: /* @__PURE__ */ h(s, {
136
+ size: 16,
137
+ stroke: n,
138
+ "aria-hidden": !0
139
+ }),
140
+ "aria-label": m,
141
+ onClick: x
142
+ }), /* @__PURE__ */ h(r, {
143
+ icon: /* @__PURE__ */ h(o, {
144
+ size: 16,
145
+ stroke: n,
146
+ "aria-hidden": !0
147
+ }),
148
+ "aria-label": p,
149
+ onClick: f
150
+ })]
151
+ }), /* @__PURE__ */ g("div", {
152
+ className: "relative min-h-[200px] w-full",
153
+ children: [
154
+ y === _.Loading && /* @__PURE__ */ h("div", {
155
+ className: "absolute inset-0 flex items-center justify-center",
156
+ children: /* @__PURE__ */ h(i, {})
157
+ }),
158
+ y === _.Error && /* @__PURE__ */ g("div", {
159
+ className: "absolute inset-0 flex flex-col items-center justify-center gap-2 p-4",
160
+ children: [/* @__PURE__ */ h(a, {
161
+ size: 40,
162
+ stroke: n,
163
+ "aria-hidden": !0,
164
+ className: "text-error"
165
+ }), /* @__PURE__ */ h("p", {
166
+ role: "alert",
167
+ className: "dial-body-text text-center text-primary",
168
+ children: v
169
+ })]
170
+ }),
171
+ y === _.Ready && b && /* @__PURE__ */ h(t, {
172
+ content: b,
173
+ errorLabel: v
174
+ })
175
+ ]
176
+ })]
177
+ });
178
+ }), O = 900 * 1e3, k = (e) => {
179
+ let t = f(/* @__PURE__ */ new Map()), n = f(/* @__PURE__ */ new Map());
180
+ return d(() => {
181
+ let r = (t) => `${e}:${t}`, i = (e, t) => `${r(e)}::${t ?? ""}`, a = (e, n) => {
182
+ let i = t.current.get(r(e));
183
+ if (i != null && i.seedKey === n && !(Date.now() - i.cachedAt > O)) return {
184
+ html: i.html,
185
+ toolResult: i.toolResult
186
+ };
187
+ }, o = (e, n, i) => {
188
+ let a = Date.now();
189
+ for (let [e, n] of t.current) a - n.cachedAt > O && t.current.delete(e);
190
+ t.current.set(r(e), {
191
+ ...n,
192
+ seedKey: i,
193
+ cachedAt: a
194
+ });
195
+ };
196
+ return {
197
+ get: a,
198
+ set: o,
199
+ invalidate: (e) => {
200
+ t.current.delete(r(e));
201
+ let i = `${r(e)}::`;
202
+ for (let e of n.current.keys()) e.startsWith(i) && n.current.delete(e);
203
+ },
204
+ getOrFetch: (e, t, r) => {
205
+ let s = a(e, t);
206
+ if (s) return Promise.resolve(s);
207
+ let c = i(e, t), l = n.current.get(c);
208
+ return l || (l = r().then((n) => (o(e, n, t), n)), n.current.set(c, l), l.finally(() => {
209
+ n.current.delete(c);
210
+ })), l;
211
+ }
212
+ };
213
+ }, [e]);
214
+ };
215
+ //#endregion
216
+ export { D as McpAppInlinePreview, _ as McpAppInlinePreviewStatus, b as collectToolCallNames, C as computeMcpAppSeedKey, x as findMcpAppForMessage, S as mcpAppCanvasKey, w as resolveMcpAppToolCallSeed, T as resolveMcpAppToolResult, E as useMcpAppInlinePreview, k as useMcpAppResponseCache };
@@ -0,0 +1,102 @@
1
+ import { McpUiHostContext } from '@mcp-ui/client';
2
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ /** Which of the host's two MCP proxy routes a deployment id resolves through — a plain toolset, or an application that is itself an MCP server. */
4
+ export type McpDeploymentKind = 'toolset' | 'application';
5
+ /**
6
+ * How a tool was discovered — determines whether it's safe to live-re-call
7
+ * (see `resolveMcpAppToolResult`) or treat as "always available" for a
8
+ * message with no other evidence (see `findMcpAppForMessage`).
9
+ *
10
+ * - `'direct'`: the active deployment itself IS this MCP server (whether
11
+ * it's a bare Toolset or an Application declaring `features.mcp`) — the
12
+ * host is talking to it directly, so which tool a mounted app's own
13
+ * invocation targets is unambiguous.
14
+ * - `'indirect'`: a different, tool-calling deployment's message happened to
15
+ * call a name prefixed with a toolset's display name; the toolset is a
16
+ * name-prefix *guess*, not a confirmed link the host was told about, so
17
+ * re-calling it live risks hitting the wrong tool or re-triggering a
18
+ * non-idempotent side effect.
19
+ */
20
+ export type McpAppToolDiscovery = 'direct' | 'indirect';
21
+ /** A tool's declared MCP Apps UI resource, keyed by the toolset it was discovered on. */
22
+ export interface McpAppToolRef {
23
+ /** Id of the toolset/application this tool was discovered on. */
24
+ toolsetId: string;
25
+ /** The tool's declared `ui://` resource. */
26
+ resourceUri: string;
27
+ /** Name used to correlate this ref against a message's real tool-call data (`resolveMcpAppToolCallSeed`/`findMcpAppForMessage`). */
28
+ toolName: string;
29
+ /** The tool's real name as declared by its owning toolset's `tools/list` — what must be passed to the mounted app/`onToolCall`. */
30
+ mcpToolName: string;
31
+ /** Deployment kind — determines which host MCP proxy route resolves `tools/call` for this tool. */
32
+ kind: McpDeploymentKind;
33
+ /** How this tool was discovered — see `McpAppToolDiscovery`. */
34
+ discovery: McpAppToolDiscovery;
35
+ }
36
+ /** Original tool call's arguments/result, seeded into the mounted app so it renders that invocation immediately instead of an empty initial state. */
37
+ export interface McpAppToolCallSeed {
38
+ /** The tool call's real structured arguments. */
39
+ toolInput?: Record<string, unknown>;
40
+ /** The tool call's result, if already known. */
41
+ toolResult?: CallToolResult;
42
+ }
43
+ /** Host-provided function that fetches a toolset's MCP Apps `ui://` resource and resolves to its HTML body. */
44
+ export type FetchMcpAppResourceHtml = (toolsetId: string, resourceUri: string) => Promise<string>;
45
+ /** Host-provided function that forwards an MCP App's tool call through the host's backend. */
46
+ export type CallMcpAppTool = (toolsetId: string, toolName: string, args: unknown, kind: McpDeploymentKind) => Promise<CallToolResult>;
47
+ /** A cached MCP App resource fetch plus its resolved tool result. */
48
+ export interface CachedMcpAppResponse {
49
+ /** The fetched resource's HTML body. */
50
+ html: string;
51
+ /** The tool call's resolved result, if any. */
52
+ toolResult?: CallToolResult;
53
+ }
54
+ /** Reads/writes `useMcpAppResponseCache`'s per-conversation cache. */
55
+ export interface McpAppResponseCache {
56
+ /**
57
+ * Returns the cached entry only if it was written for this exact
58
+ * `seedKey` (`computeMcpAppSeedKey`) and is younger than the cache's TTL;
59
+ * otherwise `undefined` (a miss), so a stale or seed-mismatched entry is
60
+ * never served.
61
+ */
62
+ get: (key: string, seedKey: string | undefined) => CachedMcpAppResponse | undefined;
63
+ /** Writes an entry for `key`, tagged with the seed it was resolved from. */
64
+ set: (key: string, value: CachedMcpAppResponse, seedKey: string | undefined) => void;
65
+ /** Removes the cached entry for `key`, if any. */
66
+ invalidate: (key: string) => void;
67
+ /**
68
+ * Returns the fresh cached entry for `(key, seedKey)` if one exists
69
+ * (same rules as `get`); otherwise runs `fetchFn` and caches its result.
70
+ * Concurrent calls for the same `(key, seedKey)` before `fetchFn` settles
71
+ * are coalesced onto the same in-flight promise instead of each starting
72
+ * their own — without this, a component re-rendered before its first
73
+ * fetch resolves (e.g. React StrictMode's dev-only double effect
74
+ * invocation) would race two independent fetches/live tool re-calls for
75
+ * the same message.
76
+ */
77
+ getOrFetch: (key: string, seedKey: string | undefined, fetchFn: () => Promise<CachedMcpAppResponse>) => Promise<CachedMcpAppResponse>;
78
+ }
79
+ /** Load state of `useMcpAppInlinePreview`'s fetch. */
80
+ export declare enum McpAppInlinePreviewStatus {
81
+ Loading = "loading",
82
+ Ready = "ready",
83
+ Error = "error",
84
+ Unavailable = "unavailable"
85
+ }
86
+ /**
87
+ * Dependencies the host must inject into `useMcpAppInlinePreview` /
88
+ * `McpAppInlinePreview` — everything that requires host context (theming,
89
+ * locale, the configured API client) rather than being derivable from the
90
+ * hook's own arguments.
91
+ */
92
+ export interface McpAppHostAdapter {
93
+ /** `McpUiHostContext` to deliver to the mounted app's `ui/initialize` handshake. */
94
+ hostContext: McpUiHostContext;
95
+ /** Operator-configured MCP Apps sandbox-proxy URL, or `null` when unconfigured. */
96
+ sandboxUrl: string | null;
97
+ /** Fetches the matched tool's MCP Apps `ui://` resource. */
98
+ fetchResourceHtml: FetchMcpAppResourceHtml;
99
+ /** Forwards a tool call the mounted app makes on its own (`onToolCall`) or the live re-call `resolveMcpAppToolResult` performs. */
100
+ callTool: CallMcpAppTool;
101
+ }
102
+ //# sourceMappingURL=mcp-apps.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-apps.d.ts","sourceRoot":"","sources":["../../src/models/mcp-apps.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE,mJAAmJ;AACnJ,MAAM,MAAM,iBAAiB,GAAG,SAAS,GAAG,aAAa,CAAC;AAE1D;;;;;;;;;;;;;;GAcG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,UAAU,CAAC;AAExD,yFAAyF;AACzF,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,oIAAoI;IACpI,QAAQ,EAAE,MAAM,CAAC;IACjB,mIAAmI;IACnI,WAAW,EAAE,MAAM,CAAC;IACpB,mGAAmG;IACnG,IAAI,EAAE,iBAAiB,CAAC;IACxB,gEAAgE;IAChE,SAAS,EAAE,mBAAmB,CAAC;CAChC;AAED,sJAAsJ;AACtJ,MAAM,WAAW,kBAAkB;IACjC,iDAAiD;IACjD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC,gDAAgD;IAChD,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,+GAA+G;AAC/G,MAAM,MAAM,uBAAuB,GAAG,CACpC,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,KAChB,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB,8FAA8F;AAC9F,MAAM,MAAM,cAAc,GAAG,CAC3B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,iBAAiB,KACpB,OAAO,CAAC,cAAc,CAAC,CAAC;AAE7B,qEAAqE;AACrE,MAAM,WAAW,oBAAoB;IACnC,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,UAAU,CAAC,EAAE,cAAc,CAAC;CAC7B;AAED,sEAAsE;AACtE,MAAM,WAAW,mBAAmB;IAClC;;;;;OAKG;IACH,GAAG,EAAE,CACH,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,oBAAoB,GAAG,SAAS,CAAC;IACtC,4EAA4E;IAC5E,GAAG,EAAE,CACH,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,KACxB,IAAI,CAAC;IACV,kDAAkD;IAClD,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC;;;;;;;;;OASG;IACH,UAAU,EAAE,CACV,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,MAAM,OAAO,CAAC,oBAAoB,CAAC,KACzC,OAAO,CAAC,oBAAoB,CAAC,CAAC;CACpC;AAED,sDAAsD;AACtD,oBAAY,yBAAyB;IACnC,OAAO,YAAY;IACnB,KAAK,UAAU;IACf,KAAK,UAAU;IACf,WAAW,gBAAgB;CAC5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,oFAAoF;IACpF,WAAW,EAAE,gBAAgB,CAAC;IAC9B,mFAAmF;IACnF,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,4DAA4D;IAC5D,iBAAiB,EAAE,uBAAuB,CAAC;IAC3C,mIAAmI;IACnI,QAAQ,EAAE,cAAc,CAAC;CAC1B"}
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@epam/ai-dial-mcp-apps",
3
+ "description": "Host-agnostic MCP Apps building blocks — tool-call/message matching, the response cache, and the inline preview hook/component shared by the full-width canvas and inline surfaces",
4
+ "version": "1.1.0-dev.405",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./index.js",
8
+ "module": "./index.js",
9
+ "types": "./index.d.ts",
10
+ "exports": {
11
+ "./package.json": "./package.json",
12
+ ".": {
13
+ "types": "./index.d.ts",
14
+ "import": "./index.js",
15
+ "default": "./index.js"
16
+ }
17
+ },
18
+ "peerDependencies": {
19
+ "@epam/ai-dial-attachment-canvas": "1.1.0-dev.405",
20
+ "@epam/ai-dial-chat-shared": "1.1.0-dev.405",
21
+ "@epam/ai-dial-ui-kit": "^0.14.0-dev.15",
22
+ "@mcp-ui/client": "^7.1.1",
23
+ "@modelcontextprotocol/sdk": "^1.27.1",
24
+ "@tabler/icons-react": "^3.44.0",
25
+ "react": "^19.0.0"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/epam/ai-dial-chat.git",
30
+ "directory": "libs/mcp-apps"
31
+ }
32
+ }
@@ -0,0 +1,93 @@
1
+ import { Message } from '@epam/ai-dial-chat-shared';
2
+ import { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
3
+ import { CallMcpAppTool, McpAppToolCallSeed, McpAppToolRef } from '../models/mcp-apps';
4
+ /**
5
+ * Collects every real tool-call name seen across `messages`' `custom_content.state`
6
+ * (in whichever known orchestrator shape is present). Used to discover an MCP-capable
7
+ * toolset that a non-MCP application delegates to internally.
8
+ */
9
+ export declare const collectToolCallNames: (messages: Message[]) => Set<string>;
10
+ /**
11
+ * Returns the `mcpAppTools` entry that best matches this message: the one
12
+ * whose name was actually called (per `custom_content.state`, in whichever
13
+ * of its two known orchestrator-specific shapes is present).
14
+ *
15
+ * If no real call matches, a `discovery: 'direct'` tool (the deployment
16
+ * itself IS this MCP server, whether it's a bare Toolset or an Application —
17
+ * see `useMcpAppTools`) always wins regardless: such a deployment's own
18
+ * response IS its MCP App responding, and it typically never populates
19
+ * `custom_content.state` at all — it isn't an LLM-orchestrated tool-call
20
+ * turn, so there is no call to find evidence of in the first place (the same
21
+ * class of self-hosted-app gap `design.md`'s D9 "Known gap" documents for
22
+ * discovery — here it recurs at per-message matching).
23
+ *
24
+ * Otherwise (a `discovery: 'indirect'` match — a real MCP-capable toolset a
25
+ * different, tool-calling deployment delegates to, guessed by name-prefix), a
26
+ * real match is required once the message has settled; while still
27
+ * streaming, `custom_content.state` may not have caught up with a tool call
28
+ * the model already made this turn, so the first such tool is guessed
29
+ * optimistically rather than popping in a beat late. Once settled, a
30
+ * deployment that finished without calling any MCP-capable toolset must
31
+ * never show the trigger, even though the deployment supports MCP Apps in
32
+ * general.
33
+ *
34
+ * Returns `undefined` for non-assistant messages or when the deployment has
35
+ * no MCP-Apps-capable tool at all.
36
+ */
37
+ export declare const findMcpAppForMessage: (message: Message, mcpAppTools: McpAppToolRef[], isStreaming: boolean) => McpAppToolRef | undefined;
38
+ /** Stable key identifying a message's MCP App canvas in an attachment canvas's `attachmentId` tracking, mirroring the `${messageIndex}:${attachmentId}` scheme used for regular attachment tiles. */
39
+ export declare const mcpAppCanvasKey: (messageIndex: number) => string;
40
+ /**
41
+ * Identifies which seed a `useMcpAppResponseCache` entry was resolved from,
42
+ * so a cache lookup can tell a settled tool-call seed apart from the
43
+ * no-tool-call-parsed-yet seed a freshly-streamed message mounts with.
44
+ */
45
+ export declare const computeMcpAppSeedKey: (toolCall: McpAppToolCallSeed | undefined) => string | undefined;
46
+ /**
47
+ * Seeds the mounted app's initial `toolInput`/`toolResult` from the
48
+ * message's real tool-call data (`custom_content.state`, in whichever of its
49
+ * two known orchestrator-specific shapes is present) — `toolInput` is the
50
+ * tool call's real structured arguments; `toolResult` is still a lossy
51
+ * wrapper around the result message's plain-text `content`, since that's
52
+ * still all the orchestrator's state carries (no `_meta` or
53
+ * `structuredContent` survives into it). When the same tool was called more
54
+ * than once this turn (e.g. the model retried after a failed first attempt
55
+ * with corrected arguments), the **last** matching call is used — an earlier
56
+ * attempt's failure is superseded, not authoritative. Returns `undefined` if
57
+ * no matching tool call is found for `toolName`.
58
+ */
59
+ export declare const resolveMcpAppToolCallSeed: (message: Message, toolName: string) => McpAppToolCallSeed | undefined;
60
+ /**
61
+ * Resolves the `toolResult` an MCP App canvas should be seeded with. Prefers
62
+ * a live re-call of the tool (through `callTool`) over `seed.toolResult`'s
63
+ * lossy plain-text reconstruction, since a host's conversation state
64
+ * typically never carries the tool's real `structuredContent` — only the
65
+ * orchestrator's flattened prose summary, which is often not enough for the
66
+ * app's UI to render anything at all.
67
+ *
68
+ * Attempted whenever the call is confirmed safe:
69
+ * - `match.discovery === 'direct'` — the deployment the host is talking to
70
+ * IS this MCP server (whether a bare Toolset or an Application), so
71
+ * `toolsetId`/`mcpToolName`/`kind` are unambiguous. Called even when `seed`
72
+ * has no `toolInput`, since a self-hosting MCP App never populates
73
+ * `custom_content.state` in the first place (see `findMcpAppForMessage`) —
74
+ * `{}` is passed instead, the same "give me your current/initial state"
75
+ * call the app's own resource would make on `ui/initialize`.
76
+ * - `seed?.toolInput != null` — a `discovery: 'indirect'` match (the toolset
77
+ * a *different*, tool-calling deployment appears to delegate to) whose
78
+ * `toolName` was matched against a *real* tool call actually seen in
79
+ * `custom_content.state` (`findMcpAppForMessage`'s name-match branch, not
80
+ * its streaming-optimistic `mcpAppTools[0]` guess). Once a real call is
81
+ * confirmed, re-calling it with the exact same `toolsetId`/args it was
82
+ * really invoked with carries no more risk than the direct case.
83
+ *
84
+ * Only the streaming-optimistic guess (`discovery: 'indirect'` with no real
85
+ * call seen yet, so `seed` is `undefined`) is skipped — that toolset/tool
86
+ * pairing is still an unconfirmed guess, and re-calling it could hit the
87
+ * wrong tool or re-trigger a non-idempotent side effect; it keeps
88
+ * `seed?.toolResult` (`undefined` here) as-is. Falls back to
89
+ * `seed?.toolResult` if an attempted live call fails (e.g. the deployment
90
+ * has no live MCP session outside the original conversation turn).
91
+ */
92
+ export declare const resolveMcpAppToolResult: (match: McpAppToolRef, seed: McpAppToolCallSeed | undefined, callTool: CallMcpAppTool) => Promise<CallToolResult | undefined>;
93
+ //# sourceMappingURL=mcp-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-app.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,OAAO,EAEb,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAClB,aAAa,EACd,MAAM,oBAAoB,CAAC;AA2D5B;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,UAAU,OAAO,EAAE,KAAG,GAAG,CAAC,MAAM,CAUpE,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,oBAAoB,GAC/B,SAAS,OAAO,EAChB,aAAa,aAAa,EAAE,EAC5B,aAAa,OAAO,KACnB,aAAa,GAAG,SAclB,CAAC;AAEF,qMAAqM;AACrM,eAAO,MAAM,eAAe,GAAI,cAAc,MAAM,KAAG,MAC5B,CAAC;AAE5B;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAC/B,UAAU,kBAAkB,GAAG,SAAS,KACvC,MAAM,GAAG,SAC+D,CAAC;AAE5E;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,yBAAyB,GACpC,SAAS,OAAO,EAChB,UAAU,MAAM,KACf,kBAAkB,GAAG,SAgBvB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,uBAAuB,GAClC,OAAO,aAAa,EACpB,MAAM,kBAAkB,GAAG,SAAS,EACpC,UAAU,cAAc,KACvB,OAAO,CAAC,cAAc,GAAG,SAAS,CAgBpC,CAAC"}