@frontmcp/uipack 1.0.0-beta.12 → 1.0.0-beta.14

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/index.js CHANGED
@@ -297,6 +297,19 @@ function generateBridgeIIFE(options = {}) {
297
297
  parts.push("});");
298
298
  parts.push("");
299
299
  parts.push("window.FrontMcpBridge = bridge;");
300
+ parts.push("function __showLoading() {");
301
+ parts.push(' var root = document.getElementById("root");');
302
+ parts.push(" if (root && !root.hasChildNodes()) {");
303
+ parts.push(
304
+ ` root.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;padding:2rem;color:#6b7280;font-family:system-ui,sans-serif"><div style="text-align:center"><div style="width:24px;height:24px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:__spin 0.6s linear infinite;margin:0 auto 12px"></div><div style="font-size:0.875rem">Loading widget...</div></div></div><style>@keyframes __spin{to{transform:rotate(360deg)}}</style>';`
305
+ );
306
+ parts.push(" }");
307
+ parts.push("}");
308
+ parts.push('if (document.readyState === "loading") {');
309
+ parts.push(' document.addEventListener("DOMContentLoaded", __showLoading);');
310
+ parts.push("} else {");
311
+ parts.push(" __showLoading();");
312
+ parts.push("}");
300
313
  parts.push("})();");
301
314
  const code = parts.join("\n");
302
315
  if (minify) {
@@ -473,7 +486,20 @@ var ExtAppsAdapter = {
473
486
  self.handleMessage(context, event);
474
487
  });
475
488
 
476
- return self.performHandshake(context);
489
+ // Defer handshake until after the document is fully loaded.
490
+ // During document.write() (used by the sandbox proxy), postMessage
491
+ // from the inner iframe may not reach the parent. Waiting for DOMContentLoaded
492
+ // or using setTimeout ensures the iframe is fully attached.
493
+ return new Promise(function(resolve) {
494
+ function doHandshake() {
495
+ self.sendHandshake(context).then(resolve, resolve);
496
+ }
497
+ if (document.readyState === 'loading') {
498
+ document.addEventListener('DOMContentLoaded', doHandshake);
499
+ } else {
500
+ setTimeout(doHandshake, 0);
501
+ }
502
+ });
477
503
  },
478
504
  handleMessage: function(context, event) {
479
505
  if (!this.isOriginTrusted(event.origin)) return;
@@ -567,32 +593,67 @@ var ExtAppsAdapter = {
567
593
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
568
594
  });
569
595
  },
570
- performHandshake: function(context) {
596
+ sendHandshake: function(context) {
597
+ // Send ui/initialize using '*' as target origin since TOFU hasn't
598
+ // been established yet. The response from the host will establish
599
+ // TOFU trust via handleMessage \u2192 isOriginTrusted.
571
600
  var self = this;
601
+ var id = ++this.requestId;
572
602
  var params = {
573
603
  appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
574
604
  appCapabilities: { tools: { listChanged: false } },
575
605
  protocolVersion: '2024-11-05'
576
606
  };
577
607
 
578
- return this.sendRequest('ui/initialize', params).then(function(result) {
579
- self.hostCapabilities = result.hostCapabilities || {};
580
- self.capabilities = Object.assign({}, self.capabilities, {
581
- canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
582
- canSendMessages: true,
583
- canOpenLinks: Boolean(self.hostCapabilities.openLink),
584
- supportsDisplayModes: true
585
- });
586
- if (result.hostContext) {
587
- Object.assign(context.hostContext, result.hostContext);
588
- }
608
+ return new Promise(function(resolve, reject) {
609
+ var timeout = setTimeout(function() {
610
+ delete self.pendingRequests[id];
611
+ // Handshake timeout is non-fatal \u2014 notifications may still arrive
612
+ resolve();
613
+ }, 10000);
614
+
615
+ self.pendingRequests[id] = {
616
+ resolve: function(result) {
617
+ self.hostCapabilities = result.hostCapabilities || {};
618
+ self.capabilities = Object.assign({}, self.capabilities, {
619
+ canCallTools: Boolean(self.hostCapabilities.serverTools || self.hostCapabilities.serverToolProxy),
620
+ canSendMessages: true,
621
+ canOpenLinks: Boolean(self.hostCapabilities.openLinks || self.hostCapabilities.openLink),
622
+ supportsDisplayModes: true
623
+ });
624
+ if (result.hostContext) {
625
+ Object.assign(context.hostContext, result.hostContext);
626
+ }
627
+ // Send ui/notifications/initialized to tell the host the view is ready.
628
+ // Per MCP Apps spec, the host waits for this before sending tool-result.
629
+ var targetOrigin = self.trustedOrigin || '*';
630
+ window.parent.postMessage({
631
+ jsonrpc: '2.0',
632
+ method: 'ui/notifications/initialized',
633
+ params: {}
634
+ }, targetOrigin);
635
+ resolve();
636
+ },
637
+ reject: function(err) { resolve(); }, // Non-fatal
638
+ timeout: timeout
639
+ };
640
+
641
+ // Use '*' for the initial handshake \u2014 we don't know the host origin yet.
642
+ // The sandbox proxy will relay this to the host.
643
+ window.parent.postMessage({
644
+ jsonrpc: '2.0', id: id, method: 'ui/initialize', params: params
645
+ }, '*');
589
646
  });
590
647
  },
648
+ performHandshake: function(context) {
649
+ return this.sendHandshake(context);
650
+ },
591
651
  callTool: function(context, name, args) {
592
- if (!this.hostCapabilities.serverToolProxy) {
652
+ if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
593
653
  return Promise.reject(new Error('Server tool proxy not supported'));
594
654
  }
595
- return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
655
+ // Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
656
+ return this.sendRequest('tools/call', { name: name, arguments: args || {} });
596
657
  },
597
658
  sendMessage: function(context, content) {
598
659
  return this.sendRequest('ui/message', { content: content });
@@ -2773,6 +2834,7 @@ function escapeAttribute(str) {
2773
2834
  function buildDataInjectionScript(options) {
2774
2835
  const { toolName, input, output, structuredContent } = options;
2775
2836
  const lines = [
2837
+ `window.__mcpAppsEnabled = true;`,
2776
2838
  `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
2777
2839
  `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
2778
2840
  `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
@@ -3103,22 +3165,71 @@ function bundleFileSource(source, filename, resolveDir, componentName) {
3103
3165
  const esbuild = require("esbuild");
3104
3166
  const mountCode = `
3105
3167
  // --- Auto-generated mount ---
3168
+ import { createElement as __h } from 'react';
3106
3169
  import { createRoot } from 'react-dom/client';
3107
3170
  import { McpBridgeProvider } from '@frontmcp/ui/react';
3108
- import React from 'react';
3109
- const __root = document.getElementById('root');
3171
+ var __root = document.getElementById('root');
3110
3172
  if (__root) {
3111
- createRoot(__root).render(
3112
- React.createElement(McpBridgeProvider, null,
3113
- React.createElement(${componentName})
3114
- )
3115
- );
3173
+ var __reactRoot = createRoot(__root);
3174
+ function __hasData(v) { return v !== undefined; }
3175
+ function __render(output) {
3176
+ __reactRoot.render(
3177
+ __h(McpBridgeProvider, null,
3178
+ __h(${componentName}, { output: output !== undefined ? output : null, input: window.__mcpToolInput, loading: !__hasData(output) })
3179
+ )
3180
+ );
3181
+ }
3182
+ // Render immediately (component shows loading state until data arrives)
3183
+ __render(undefined);
3184
+ // 1. Try OpenAI SDK (toolOutput set synchronously or after load)
3185
+ if (typeof window !== 'undefined') {
3186
+ if (!window.openai) window.openai = {};
3187
+ var __cur = window.openai.toolOutput;
3188
+ if (__hasData(__cur)) { __render(__cur); }
3189
+ Object.defineProperty(window.openai, 'toolOutput', {
3190
+ get: function() { return __cur; },
3191
+ set: function(v) { __cur = v; __render(v); },
3192
+ configurable: true, enumerable: true
3193
+ });
3194
+ }
3195
+ // 2. Try injected data globals
3196
+ if (__hasData(window.__mcpToolOutput)) { __render(window.__mcpToolOutput); }
3197
+ // 3. Listen for bridge tool-result (ext-apps / MCP Inspector)
3198
+ var __bridge = window.FrontMcpBridge;
3199
+ if (__bridge && typeof __bridge.onToolResult === 'function') {
3200
+ __bridge.onToolResult(function(data) { __render(data); });
3201
+ } else {
3202
+ // 4. Fallback: listen for tool:result CustomEvent (standalone / MCP Inspector)
3203
+ window.addEventListener('tool:result', function(e) {
3204
+ var d = e.detail;
3205
+ if (d) __render(d.structuredContent !== undefined ? d.structuredContent : d.content !== undefined ? d.content : d);
3206
+ });
3207
+ }
3116
3208
  }`;
3117
3209
  const loader = {
3118
3210
  ".tsx": "tsx",
3119
3211
  ".jsx": "jsx"
3120
3212
  };
3121
3213
  const stdinLoader = filename.endsWith(".tsx") ? "tsx" : "jsx";
3214
+ const alias = {};
3215
+ try {
3216
+ const nodePath = require("path");
3217
+ const nodeFs = require("fs");
3218
+ const candidates = [
3219
+ nodePath.join(process.cwd(), "node_modules", "@frontmcp", "ui", "dist", "esm"),
3220
+ nodePath.join(resolveDir, "node_modules", "@frontmcp", "ui", "dist", "esm")
3221
+ ];
3222
+ for (const uiEsmBase of candidates) {
3223
+ if (!nodeFs.existsSync(uiEsmBase)) continue;
3224
+ const subpaths = ["components", "react", "theme", "bridge", "runtime"];
3225
+ for (const sub of subpaths) {
3226
+ const mjs = nodePath.join(uiEsmBase, sub, "index.mjs");
3227
+ if (nodeFs.existsSync(mjs)) alias[`@frontmcp/ui/${sub}`] = mjs;
3228
+ }
3229
+ if (Object.keys(alias).length > 0) break;
3230
+ }
3231
+ } catch {
3232
+ }
3122
3233
  try {
3123
3234
  const result = esbuild.buildSync({
3124
3235
  stdin: {
@@ -3131,10 +3242,9 @@ if (__root) {
3131
3242
  write: false,
3132
3243
  format: "esm",
3133
3244
  target: "es2020",
3134
- jsx: "transform",
3135
- jsxFactory: "React.createElement",
3136
- jsxFragment: "React.Fragment",
3137
- external: ["react", "react-dom"],
3245
+ jsx: "automatic",
3246
+ external: ["react", "react-dom", "react/jsx-runtime", "react/jsx-dev-runtime"],
3247
+ alias,
3138
3248
  define: { "process.env.NODE_ENV": '"production"' },
3139
3249
  platform: "browser",
3140
3250
  treeShaking: true,
@@ -3657,8 +3767,26 @@ function wrapDetectedContent(value) {
3657
3767
  }
3658
3768
 
3659
3769
  // libs/uipack/src/adapters/template-renderer.ts
3770
+ function buildCspConfig(resolver) {
3771
+ const cspResourceDomains = ["https://esm.sh"];
3772
+ const cspConnectDomains = ["https://esm.sh"];
3773
+ if (resolver && "overrides" in resolver) {
3774
+ const overrides = resolver.overrides;
3775
+ if (overrides) {
3776
+ for (const url of Object.values(overrides)) {
3777
+ try {
3778
+ const origin = new URL(url).origin;
3779
+ if (!cspResourceDomains.includes(origin)) cspResourceDomains.push(origin);
3780
+ if (!cspConnectDomains.includes(origin)) cspConnectDomains.push(origin);
3781
+ } catch {
3782
+ }
3783
+ }
3784
+ }
3785
+ }
3786
+ return { resourceDomains: cspResourceDomains, connectDomains: cspConnectDomains };
3787
+ }
3660
3788
  function renderToolTemplate(options) {
3661
- const { toolName, input, output, template, platformType, resolver } = options;
3789
+ const { toolName, input, output, template, resolver } = options;
3662
3790
  const uiType = detectUIType(template);
3663
3791
  const shellConfig = {
3664
3792
  toolName,
@@ -3671,25 +3799,7 @@ function renderToolTemplate(options) {
3671
3799
  let hash = "";
3672
3800
  let size = 0;
3673
3801
  if (typeof template === "object" && template !== null && "file" in template) {
3674
- const cspResourceDomains = ["https://esm.sh"];
3675
- const cspConnectDomains = ["https://esm.sh"];
3676
- if (resolver && "overrides" in resolver) {
3677
- const overrides = resolver.overrides;
3678
- if (overrides) {
3679
- for (const url of Object.values(overrides)) {
3680
- try {
3681
- const origin = new URL(url).origin;
3682
- if (!cspResourceDomains.includes(origin)) cspResourceDomains.push(origin);
3683
- if (!cspConnectDomains.includes(origin)) cspConnectDomains.push(origin);
3684
- } catch {
3685
- }
3686
- }
3687
- }
3688
- }
3689
- const cspConfig = {
3690
- resourceDomains: cspResourceDomains,
3691
- connectDomains: cspConnectDomains
3692
- };
3802
+ const cspConfig = buildCspConfig(resolver);
3693
3803
  const result = renderComponent({ source: template }, { ...shellConfig, csp: cspConfig });
3694
3804
  html = result.html;
3695
3805
  hash = result.hash;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@frontmcp/uipack",
3
- "version": "1.0.0-beta.12",
3
+ "version": "1.0.0-beta.14",
4
4
  "description": "FrontMCP UIpack - HTML shell builder, pluggable import resolver, and NPM component loader for MCP UI (React-free core)",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -1 +1 @@
1
- {"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GAAG,MAAM,CAWT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,gCAAgC;IAChC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7D,kCAAkC;IAClC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,qCAAqC;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;CACtC;AAID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA6BvD"}
1
+ {"version":3,"file":"data-injector.d.ts","sourceRoot":"","sources":["../../src/shell/data-injector.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GAAG,MAAM,CAYT;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,CAAC;IACrC,gCAAgC;IAChC,UAAU,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC7D,kCAAkC;IAClC,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9D,4CAA4C;IAC5C,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,qCAAqC;IACrC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,MAAM,CAAC;CACtC;AAID;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,eAAe,CA6BvD"}
package/shell/index.js CHANGED
@@ -162,6 +162,7 @@ function safeJsonForScript(value) {
162
162
  function buildDataInjectionScript(options) {
163
163
  const { toolName, input, output, structuredContent } = options;
164
164
  const lines = [
165
+ `window.__mcpAppsEnabled = true;`,
165
166
  `window.__mcpToolName = ${safeJsonForScript(toolName)};`,
166
167
  `window.__mcpToolInput = ${safeJsonForScript(input ?? null)};`,
167
168
  `window.__mcpToolOutput = ${safeJsonForScript(output ?? null)};`,
@@ -252,6 +253,19 @@ function generateBridgeIIFE(options = {}) {
252
253
  parts.push("});");
253
254
  parts.push("");
254
255
  parts.push("window.FrontMcpBridge = bridge;");
256
+ parts.push("function __showLoading() {");
257
+ parts.push(' var root = document.getElementById("root");');
258
+ parts.push(" if (root && !root.hasChildNodes()) {");
259
+ parts.push(
260
+ ` root.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;padding:2rem;color:#6b7280;font-family:system-ui,sans-serif"><div style="text-align:center"><div style="width:24px;height:24px;border:2px solid #e5e7eb;border-top-color:#3b82f6;border-radius:50%;animation:__spin 0.6s linear infinite;margin:0 auto 12px"></div><div style="font-size:0.875rem">Loading widget...</div></div></div><style>@keyframes __spin{to{transform:rotate(360deg)}}</style>';`
261
+ );
262
+ parts.push(" }");
263
+ parts.push("}");
264
+ parts.push('if (document.readyState === "loading") {');
265
+ parts.push(' document.addEventListener("DOMContentLoaded", __showLoading);');
266
+ parts.push("} else {");
267
+ parts.push(" __showLoading();");
268
+ parts.push("}");
255
269
  parts.push("})();");
256
270
  const code = parts.join("\n");
257
271
  if (minify) {
@@ -428,7 +442,20 @@ var ExtAppsAdapter = {
428
442
  self.handleMessage(context, event);
429
443
  });
430
444
 
431
- return self.performHandshake(context);
445
+ // Defer handshake until after the document is fully loaded.
446
+ // During document.write() (used by the sandbox proxy), postMessage
447
+ // from the inner iframe may not reach the parent. Waiting for DOMContentLoaded
448
+ // or using setTimeout ensures the iframe is fully attached.
449
+ return new Promise(function(resolve) {
450
+ function doHandshake() {
451
+ self.sendHandshake(context).then(resolve, resolve);
452
+ }
453
+ if (document.readyState === 'loading') {
454
+ document.addEventListener('DOMContentLoaded', doHandshake);
455
+ } else {
456
+ setTimeout(doHandshake, 0);
457
+ }
458
+ });
432
459
  },
433
460
  handleMessage: function(context, event) {
434
461
  if (!this.isOriginTrusted(event.origin)) return;
@@ -522,32 +549,67 @@ var ExtAppsAdapter = {
522
549
  window.parent.postMessage({ jsonrpc: '2.0', id: id, method: method, params: params }, targetOrigin);
523
550
  });
524
551
  },
525
- performHandshake: function(context) {
552
+ sendHandshake: function(context) {
553
+ // Send ui/initialize using '*' as target origin since TOFU hasn't
554
+ // been established yet. The response from the host will establish
555
+ // TOFU trust via handleMessage \u2192 isOriginTrusted.
526
556
  var self = this;
557
+ var id = ++this.requestId;
527
558
  var params = {
528
559
  appInfo: { name: 'FrontMCP Widget', version: '1.0.0' },
529
560
  appCapabilities: { tools: { listChanged: false } },
530
561
  protocolVersion: '2024-11-05'
531
562
  };
532
563
 
533
- return this.sendRequest('ui/initialize', params).then(function(result) {
534
- self.hostCapabilities = result.hostCapabilities || {};
535
- self.capabilities = Object.assign({}, self.capabilities, {
536
- canCallTools: Boolean(self.hostCapabilities.serverToolProxy),
537
- canSendMessages: true,
538
- canOpenLinks: Boolean(self.hostCapabilities.openLink),
539
- supportsDisplayModes: true
540
- });
541
- if (result.hostContext) {
542
- Object.assign(context.hostContext, result.hostContext);
543
- }
564
+ return new Promise(function(resolve, reject) {
565
+ var timeout = setTimeout(function() {
566
+ delete self.pendingRequests[id];
567
+ // Handshake timeout is non-fatal \u2014 notifications may still arrive
568
+ resolve();
569
+ }, 10000);
570
+
571
+ self.pendingRequests[id] = {
572
+ resolve: function(result) {
573
+ self.hostCapabilities = result.hostCapabilities || {};
574
+ self.capabilities = Object.assign({}, self.capabilities, {
575
+ canCallTools: Boolean(self.hostCapabilities.serverTools || self.hostCapabilities.serverToolProxy),
576
+ canSendMessages: true,
577
+ canOpenLinks: Boolean(self.hostCapabilities.openLinks || self.hostCapabilities.openLink),
578
+ supportsDisplayModes: true
579
+ });
580
+ if (result.hostContext) {
581
+ Object.assign(context.hostContext, result.hostContext);
582
+ }
583
+ // Send ui/notifications/initialized to tell the host the view is ready.
584
+ // Per MCP Apps spec, the host waits for this before sending tool-result.
585
+ var targetOrigin = self.trustedOrigin || '*';
586
+ window.parent.postMessage({
587
+ jsonrpc: '2.0',
588
+ method: 'ui/notifications/initialized',
589
+ params: {}
590
+ }, targetOrigin);
591
+ resolve();
592
+ },
593
+ reject: function(err) { resolve(); }, // Non-fatal
594
+ timeout: timeout
595
+ };
596
+
597
+ // Use '*' for the initial handshake \u2014 we don't know the host origin yet.
598
+ // The sandbox proxy will relay this to the host.
599
+ window.parent.postMessage({
600
+ jsonrpc: '2.0', id: id, method: 'ui/initialize', params: params
601
+ }, '*');
544
602
  });
545
603
  },
604
+ performHandshake: function(context) {
605
+ return this.sendHandshake(context);
606
+ },
546
607
  callTool: function(context, name, args) {
547
- if (!this.hostCapabilities.serverToolProxy) {
608
+ if (!this.hostCapabilities.serverTools && !this.hostCapabilities.serverToolProxy) {
548
609
  return Promise.reject(new Error('Server tool proxy not supported'));
549
610
  }
550
- return this.sendRequest('ui/callServerTool', { name: name, arguments: args });
611
+ // Per ext-apps spec: use standard MCP method 'tools/call' (not 'ui/callServerTool')
612
+ return this.sendRequest('tools/call', { name: name, arguments: args || {} });
551
613
  },
552
614
  sendMessage: function(context, content) {
553
615
  return this.sendRequest('ui/message', { content: content });