@mcp-ts/sdk 1.3.9 → 1.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 (47) hide show
  1. package/README.md +0 -1
  2. package/dist/adapters/langchain-adapter.js.map +1 -1
  3. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  4. package/dist/client/index.d.mts +3 -189
  5. package/dist/client/index.d.ts +3 -189
  6. package/dist/client/index.js +218 -54
  7. package/dist/client/index.js.map +1 -1
  8. package/dist/client/index.mjs +215 -55
  9. package/dist/client/index.mjs.map +1 -1
  10. package/dist/client/react.d.mts +29 -40
  11. package/dist/client/react.d.ts +29 -40
  12. package/dist/client/react.js +492 -147
  13. package/dist/client/react.js.map +1 -1
  14. package/dist/client/react.mjs +490 -149
  15. package/dist/client/react.mjs.map +1 -1
  16. package/dist/client/vue.d.mts +3 -2
  17. package/dist/client/vue.d.ts +3 -2
  18. package/dist/client/vue.js +239 -63
  19. package/dist/client/vue.js.map +1 -1
  20. package/dist/client/vue.mjs +236 -64
  21. package/dist/client/vue.mjs.map +1 -1
  22. package/dist/index-CQr9q0bF.d.mts +295 -0
  23. package/dist/index-nE_7Io0I.d.ts +295 -0
  24. package/dist/index.d.mts +2 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +315 -64
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +303 -65
  29. package/dist/index.mjs.map +1 -1
  30. package/dist/server/index.js +93 -10
  31. package/dist/server/index.js.map +1 -1
  32. package/dist/server/index.mjs +88 -10
  33. package/dist/server/index.mjs.map +1 -1
  34. package/package.json +13 -11
  35. package/src/adapters/langchain-adapter.ts +1 -1
  36. package/src/client/core/app-host.ts +252 -65
  37. package/src/client/core/constants.ts +30 -0
  38. package/src/client/index.ts +6 -1
  39. package/src/client/react/index.ts +6 -1
  40. package/src/client/react/use-app-host.ts +13 -19
  41. package/src/client/react/use-mcp-apps.tsx +297 -125
  42. package/src/client/react/use-mcp.ts +75 -36
  43. package/src/client/utils/app-host-utils.ts +62 -0
  44. package/src/client/vue/use-mcp.ts +23 -12
  45. package/src/server/mcp/oauth-client.ts +31 -8
  46. package/src/server/storage/crypto.ts +92 -0
  47. package/src/server/storage/supabase-backend.ts +7 -6
package/dist/index.js CHANGED
@@ -8,6 +8,7 @@ var auth_js = require('@modelcontextprotocol/sdk/client/auth.js');
8
8
  var types_js = require('@modelcontextprotocol/sdk/types.js');
9
9
  var fs2 = require('fs');
10
10
  var path = require('path');
11
+ var crypto = require('crypto');
11
12
  var appBridge = require('@modelcontextprotocol/ext-apps/app-bridge');
12
13
 
13
14
  function _interopNamespace(e) {
@@ -771,6 +772,77 @@ var SqliteStorage = class {
771
772
 
772
773
  // src/server/storage/supabase-backend.ts
773
774
  init_cjs_shims();
775
+
776
+ // src/server/storage/crypto.ts
777
+ init_cjs_shims();
778
+ var ALGORITHM = "aes-256-gcm";
779
+ var IV_LENGTH = 12;
780
+ var ENCRYPTION_PREFIX = "enc:1:";
781
+ var warningLogged = false;
782
+ function getKey() {
783
+ const keyString = process.env.STORAGE_ENCRYPTION_KEY;
784
+ if (!keyString) return null;
785
+ if (keyString.length === 64) {
786
+ return Buffer.from(keyString, "hex");
787
+ } else {
788
+ const keyBuffer = Buffer.alloc(32);
789
+ keyBuffer.write(keyString, 0, 32, "utf-8");
790
+ return keyBuffer;
791
+ }
792
+ }
793
+ function encryptObject(data) {
794
+ if (data === void 0 || data === null) return data;
795
+ const key = getKey();
796
+ if (!key) {
797
+ if (!warningLogged) {
798
+ console.warn("[mcp-ts][Storage] WARNING: STORAGE_ENCRYPTION_KEY is not set. Saving sensitive data in plain-text.");
799
+ warningLogged = true;
800
+ }
801
+ return data;
802
+ }
803
+ try {
804
+ const text = JSON.stringify(data);
805
+ const iv = crypto.randomBytes(IV_LENGTH);
806
+ const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
807
+ let encrypted = cipher.update(text, "utf-8", "hex");
808
+ encrypted += cipher.final("hex");
809
+ const authTag = cipher.getAuthTag().toString("hex");
810
+ return `${ENCRYPTION_PREFIX}${iv.toString("hex")}:${authTag}:${encrypted}`;
811
+ } catch (e) {
812
+ console.error("[mcp-ts][Storage] Encryption failed, falling back to plain-text.", e);
813
+ return data;
814
+ }
815
+ }
816
+ function decryptObject(data) {
817
+ if (data === void 0 || data === null) return data;
818
+ if (typeof data !== "string" || !data.startsWith(ENCRYPTION_PREFIX)) {
819
+ return data;
820
+ }
821
+ const key = getKey();
822
+ if (!key) {
823
+ console.warn("[mcp-ts][Storage] WARNING: Found encrypted data but STORAGE_ENCRYPTION_KEY is missing. Returning raw encrypted string.");
824
+ return data;
825
+ }
826
+ try {
827
+ const parts = data.split(":");
828
+ if (parts.length !== 5) {
829
+ return data;
830
+ }
831
+ const iv = Buffer.from(parts[2], "hex");
832
+ const authTag = Buffer.from(parts[3], "hex");
833
+ const encryptedText = parts[4];
834
+ const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
835
+ decipher.setAuthTag(authTag);
836
+ let decrypted = decipher.update(encryptedText, "hex", "utf-8");
837
+ decrypted += decipher.final("utf-8");
838
+ return JSON.parse(decrypted);
839
+ } catch (e) {
840
+ console.error("[mcp-ts][Storage] Decryption failed.", e);
841
+ return data;
842
+ }
843
+ }
844
+
845
+ // src/server/storage/supabase-backend.ts
774
846
  var SupabaseStorageBackend = class {
775
847
  constructor(supabase) {
776
848
  this.supabase = supabase;
@@ -801,10 +873,10 @@ var SupabaseStorageBackend = class {
801
873
  callbackUrl: row.callback_url,
802
874
  createdAt: new Date(row.created_at).getTime(),
803
875
  identity: row.identity,
804
- headers: row.headers,
876
+ headers: decryptObject(row.headers),
805
877
  active: row.active,
806
878
  clientInformation: row.client_information,
807
- tokens: row.tokens,
879
+ tokens: decryptObject(row.tokens),
808
880
  codeVerifier: row.code_verifier,
809
881
  clientId: row.client_id
810
882
  };
@@ -825,10 +897,10 @@ var SupabaseStorageBackend = class {
825
897
  callback_url: session.callbackUrl,
826
898
  created_at: new Date(session.createdAt || Date.now()).toISOString(),
827
899
  identity,
828
- headers: session.headers,
900
+ headers: encryptObject(session.headers),
829
901
  active: session.active ?? false,
830
902
  client_information: session.clientInformation,
831
- tokens: session.tokens,
903
+ tokens: encryptObject(session.tokens),
832
904
  code_verifier: session.codeVerifier,
833
905
  client_id: session.clientId,
834
906
  expires_at: expiresAt
@@ -853,9 +925,9 @@ var SupabaseStorageBackend = class {
853
925
  if ("transportType" in data) updateData.transport_type = data.transportType;
854
926
  if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
855
927
  if ("active" in data) updateData.active = data.active;
856
- if ("headers" in data) updateData.headers = data.headers;
928
+ if ("headers" in data) updateData.headers = encryptObject(data.headers);
857
929
  if ("clientInformation" in data) updateData.client_information = data.clientInformation;
858
- if ("tokens" in data) updateData.tokens = data.tokens;
930
+ if ("tokens" in data) updateData.tokens = encryptObject(data.tokens);
859
931
  if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
860
932
  if ("clientId" in data) updateData.client_id = data.clientId;
861
933
  const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("identity", identity).eq("session_id", sessionId).select("id");
@@ -1722,13 +1794,24 @@ var MCPClient = class _MCPClient {
1722
1794
  }
1723
1795
  } catch (error) {
1724
1796
  if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
1725
- this.emitStateChange("AUTHENTICATING");
1726
- console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1727
- await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1728
1797
  let authUrl = "";
1729
1798
  if (this.oauthProvider) {
1730
- authUrl = this.oauthProvider.authUrl || "";
1799
+ authUrl = (this.oauthProvider.authUrl || "").trim();
1731
1800
  }
1801
+ if (!authUrl) {
1802
+ const detail = error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : "Unauthorized";
1803
+ const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
1804
+ this.emitError(message, "auth");
1805
+ this.emitStateChange("FAILED");
1806
+ try {
1807
+ await storage.removeSession(this.identity, this.sessionId);
1808
+ } catch {
1809
+ }
1810
+ throw new Error(message);
1811
+ }
1812
+ this.emitStateChange("AUTHENTICATING");
1813
+ console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1814
+ await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1732
1815
  if (this.serverId) {
1733
1816
  this._onConnectionEvent.fire({
1734
1817
  type: "auth_required",
@@ -3281,22 +3364,92 @@ var SSEClient = class {
3281
3364
 
3282
3365
  // src/client/core/app-host.ts
3283
3366
  init_cjs_shims();
3284
- var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
3285
- var SANDBOX_PERMISSIONS = [
3286
- "allow-scripts",
3287
- // Required for app JavaScript execution
3288
- "allow-forms",
3289
- // Required for form submissions
3290
- "allow-same-origin",
3291
- // Required for Blob URL correctness
3292
- "allow-modals",
3293
- // Required for dialogs/alerts
3294
- "allow-popups",
3295
- // Required for opening links
3296
- "allow-downloads"
3297
- // Required for file downloads
3298
- ].join(" ");
3299
- var MCP_URI_SCHEMES = ["ui://", "mcp-app://"];
3367
+
3368
+ // src/client/utils/app-host-utils.ts
3369
+ init_cjs_shims();
3370
+
3371
+ // src/client/core/constants.ts
3372
+ init_cjs_shims();
3373
+ var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
3374
+ var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
3375
+ var APP_HOST_DEFAULTS = {
3376
+ /** Default timeout for waiting for the sandbox proxy to be ready (ms). */
3377
+ SANDBOX_TIMEOUT_MS: 1e4,
3378
+ /** Default host info reported to guest apps. */
3379
+ HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
3380
+ /** Supported MCP App URI schemes. */
3381
+ URI_SCHEMES: ["ui://", "mcp-app://"],
3382
+ /** Default theme for the host context. */
3383
+ THEME: "dark",
3384
+ /** Default platform for the host context. */
3385
+ PLATFORM: "web",
3386
+ /** Default max height for the iframe container (px). */
3387
+ MAX_HEIGHT: 6e3
3388
+ };
3389
+
3390
+ // src/client/utils/app-host-utils.ts
3391
+ var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
3392
+ async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
3393
+ iframe.style.width = "100%";
3394
+ iframe.style.height = "100%";
3395
+ iframe.style.border = "none";
3396
+ iframe.style.backgroundColor = "transparent";
3397
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
3398
+ const onReady = new Promise((resolve, reject) => {
3399
+ let settled = false;
3400
+ const cleanup = () => {
3401
+ window.removeEventListener("message", messageListener);
3402
+ iframe.removeEventListener("error", errorListener);
3403
+ };
3404
+ const timeoutId = setTimeout(() => {
3405
+ if (!settled) {
3406
+ settled = true;
3407
+ cleanup();
3408
+ reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
3409
+ }
3410
+ }, DEFAULT_SANDBOX_TIMEOUT_MS);
3411
+ const messageListener = (event) => {
3412
+ if (event.source === iframe.contentWindow) {
3413
+ if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
3414
+ if (!settled) {
3415
+ settled = true;
3416
+ clearTimeout(timeoutId);
3417
+ cleanup();
3418
+ resolve();
3419
+ }
3420
+ }
3421
+ }
3422
+ };
3423
+ const errorListener = () => {
3424
+ if (!settled) {
3425
+ settled = true;
3426
+ clearTimeout(timeoutId);
3427
+ cleanup();
3428
+ reject(new Error("Failed to load sandbox proxy iframe"));
3429
+ }
3430
+ };
3431
+ window.addEventListener("message", messageListener);
3432
+ iframe.addEventListener("error", errorListener);
3433
+ });
3434
+ iframe.src = sandboxProxyUrl.href;
3435
+ return { onReady };
3436
+ }
3437
+
3438
+ // src/client/core/app-host.ts
3439
+ var DEFAULT_MCP_APP_CSP = {
3440
+ "default-src": "'self'",
3441
+ "script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
3442
+ "style-src": "'self' 'unsafe-inline' https:",
3443
+ "connect-src": "'self' https: wss:",
3444
+ "img-src": "'self' data: https: blob:",
3445
+ "font-src": "'self' data: https:",
3446
+ "media-src": "'self' https: blob:",
3447
+ "frame-src": "'none'",
3448
+ "object-src": "'none'",
3449
+ "base-uri": "'self'"
3450
+ };
3451
+ var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
3452
+ var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
3300
3453
  var AppHost = class {
3301
3454
  constructor(client, iframe, options) {
3302
3455
  this.client = client;
@@ -3305,10 +3458,12 @@ var AppHost = class {
3305
3458
  __publicField(this, "sessionId");
3306
3459
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
3307
3460
  __publicField(this, "debug");
3308
- /** Callback for app messages (e.g., chat messages from the app) */
3461
+ __publicField(this, "sandboxConfig");
3462
+ __publicField(this, "options");
3309
3463
  __publicField(this, "onAppMessage");
3310
- this.debug = options?.debug ?? false;
3311
- this.configureSandbox();
3464
+ this.options = options || {};
3465
+ this.debug = this.options.debug ?? false;
3466
+ this.sandboxConfig = this.options.sandbox;
3312
3467
  this.bridge = this.initializeBridge();
3313
3468
  }
3314
3469
  // ============================================
@@ -3335,19 +3490,35 @@ var AppHost = class {
3335
3490
  }
3336
3491
  }
3337
3492
  /**
3338
- * Launch an MCP App from a URL or MCP resource URI.
3493
+ * Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
3339
3494
  * Loads the HTML first, then establishes bridge connection.
3340
3495
  */
3341
- async launch(url, sessionId) {
3496
+ async launch(source, sessionId) {
3342
3497
  if (sessionId) this.sessionId = sessionId;
3343
3498
  const initializedPromise = this.onAppReady();
3344
- if (this.isMcpUri(url)) {
3345
- await this.launchMcpApp(url);
3346
- } else {
3347
- this.iframe.src = url;
3499
+ let htmlToRender = source.html;
3500
+ if (!htmlToRender && source.uri) {
3501
+ if (this.isMcpUri(source.uri)) {
3502
+ htmlToRender = await this.readMcpAppHtml(source.uri);
3503
+ }
3504
+ }
3505
+ if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
3506
+ this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
3507
+ this.iframe.src = source.uri;
3508
+ await this.onIframeReady();
3509
+ await this.connectBridge();
3510
+ } else if (htmlToRender) {
3511
+ if (!this.sandboxConfig) {
3512
+ throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
3513
+ }
3514
+ await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
3515
+ await this.connectBridge();
3516
+ this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
3517
+ await this.bridge.sendSandboxResourceReady({
3518
+ html: htmlToRender,
3519
+ csp: this.sandboxConfig.csp
3520
+ });
3348
3521
  }
3349
- await this.onIframeReady();
3350
- await this.connectBridge();
3351
3522
  this.log("Waiting for app initialization");
3352
3523
  await Promise.race([
3353
3524
  initializedPromise,
@@ -3358,6 +3529,19 @@ var AppHost = class {
3358
3529
  ]);
3359
3530
  this.log("App launched and ready");
3360
3531
  }
3532
+ // Set host context manually
3533
+ setHostContext(context) {
3534
+ this.options.hostContext = context;
3535
+ if (this.bridge) {
3536
+ this.bridge.setHostContext(context);
3537
+ }
3538
+ }
3539
+ // Send streaming inputs manually
3540
+ sendToolInputPartial(params) {
3541
+ if (this.bridge) {
3542
+ this.bridge.sendToolInputPartial(params);
3543
+ }
3544
+ }
3361
3545
  /**
3362
3546
  * Wait for app to signal initialization complete
3363
3547
  */
@@ -3408,14 +3592,17 @@ var AppHost = class {
3408
3592
  this.log("Sending tool cancellation to app");
3409
3593
  this.bridge.sendToolCancelled({ reason });
3410
3594
  }
3595
+ /**
3596
+ * Tell the guest UI the resource is being torn down (unload / cleanup).
3597
+ * Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
3598
+ */
3599
+ teardownResource(params = {}) {
3600
+ this.log("Sending resource teardown to app");
3601
+ this.bridge.teardownResource(params);
3602
+ }
3411
3603
  // ============================================
3412
3604
  // Private: Initialization
3413
3605
  // ============================================
3414
- configureSandbox() {
3415
- if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
3416
- this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
3417
- }
3418
- }
3419
3606
  initializeBridge() {
3420
3607
  const bridge = new appBridge.AppBridge(
3421
3608
  null,
@@ -3424,12 +3611,10 @@ var AppHost = class {
3424
3611
  openLinks: {},
3425
3612
  serverTools: {},
3426
3613
  logging: {},
3427
- // Declare support for model context updates
3428
3614
  updateModelContext: { text: {} }
3429
3615
  },
3430
3616
  {
3431
- // Initial host context
3432
- hostContext: {
3617
+ hostContext: this.options.hostContext || {
3433
3618
  theme: "dark",
3434
3619
  platform: "web",
3435
3620
  containerDimensions: { maxHeight: 6e3 },
@@ -3438,19 +3623,56 @@ var AppHost = class {
3438
3623
  }
3439
3624
  }
3440
3625
  );
3626
+ bridge.fallbackRequestHandler = this.options.onFallbackRequest;
3441
3627
  bridge.oncalltool = (params) => this.handleToolCall(params);
3442
- bridge.onopenlink = this.handleOpenLink.bind(this);
3443
- bridge.onmessage = this.handleMessage.bind(this);
3444
- bridge.onloggingmessage = (params) => this.log(`App log [${params.level}]: ${params.data}`);
3628
+ if (this.options.onReadResource) {
3629
+ bridge.onreadresource = async (params) => {
3630
+ const resp = await this.options.onReadResource(params.uri);
3631
+ return {
3632
+ contents: resp.contents.map((c) => ({
3633
+ uri: params.uri,
3634
+ text: c.text,
3635
+ blob: c.blob
3636
+ }))
3637
+ };
3638
+ };
3639
+ }
3640
+ bridge.onopenlink = async (params, extra) => {
3641
+ if (this.options.onOpenLink) {
3642
+ return await this.options.onOpenLink(params, extra);
3643
+ }
3644
+ return this.handleOpenLink(params);
3645
+ };
3646
+ bridge.onmessage = async (params, extra) => {
3647
+ if (this.options.onMessage) {
3648
+ return await this.options.onMessage(params, extra);
3649
+ }
3650
+ return this.handleMessage(params);
3651
+ };
3652
+ bridge.onloggingmessage = (params) => {
3653
+ this.log(`App log [${params.level}]: ${params.data}`);
3654
+ if (this.options.onLoggingMessage) {
3655
+ this.options.onLoggingMessage(params);
3656
+ }
3657
+ };
3445
3658
  bridge.onupdatemodelcontext = async () => ({});
3446
- bridge.onsizechange = async ({ width, height }) => {
3447
- if (height !== void 0) this.iframe.style.height = `${height}px`;
3448
- if (width !== void 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
3659
+ bridge.onsizechange = async (params) => {
3660
+ const { width, height } = params;
3661
+ if (height !== void 0 && height > 0) {
3662
+ this.iframe.style.height = `${height}px`;
3663
+ }
3664
+ if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
3665
+ if (this.options.onSizeChanged) {
3666
+ this.options.onSizeChanged(params);
3667
+ }
3449
3668
  return {};
3450
3669
  };
3451
- bridge.onrequestdisplaymode = async (params) => ({
3452
- mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
3453
- });
3670
+ bridge.onrequestdisplaymode = async (params, extra) => {
3671
+ if (this.options.onRequestDisplayMode) {
3672
+ return await this.options.onRequestDisplayMode(params, extra);
3673
+ }
3674
+ return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
3675
+ };
3454
3676
  return bridge;
3455
3677
  }
3456
3678
  async connectBridge() {
@@ -3464,6 +3686,9 @@ var AppHost = class {
3464
3686
  this.log("Bridge connected successfully");
3465
3687
  } catch (error) {
3466
3688
  this.log("Bridge connection failed", "error");
3689
+ if (this.options.onError) {
3690
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
3691
+ }
3467
3692
  throw error;
3468
3693
  }
3469
3694
  }
@@ -3471,8 +3696,11 @@ var AppHost = class {
3471
3696
  // Private: Bridge Event Handlers
3472
3697
  // ============================================
3473
3698
  async handleToolCall(params) {
3474
- if (!this.client.isConnected()) {
3475
- throw new Error("Client disconnected");
3699
+ if (this.options.onCallTool) {
3700
+ return await this.options.onCallTool(params);
3701
+ }
3702
+ if (!this.client || !this.client.isConnected()) {
3703
+ throw new Error("Client disconnected or not provided");
3476
3704
  }
3477
3705
  const sessionId = await this.getSessionId();
3478
3706
  if (!sessionId) {
@@ -3496,13 +3724,19 @@ var AppHost = class {
3496
3724
  // ============================================
3497
3725
  // Private: Resource Loading
3498
3726
  // ============================================
3499
- async launchMcpApp(uri) {
3500
- if (!this.client.isConnected()) {
3501
- throw new Error("Client must be connected");
3727
+ async launchSandboxedHtml(html, config) {
3728
+ const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
3729
+ const url = new URL(sandboxUrlString, globalThis.location?.href);
3730
+ if (config.csp && Object.keys(config.csp).length > 0) {
3731
+ url.searchParams.set("csp", JSON.stringify(config.csp));
3502
3732
  }
3733
+ const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
3734
+ await onReady;
3735
+ }
3736
+ async readMcpAppHtml(uri) {
3503
3737
  const sessionId = await this.getSessionId();
3504
- if (!sessionId) {
3505
- throw new Error("No active session");
3738
+ if (!sessionId && !this.options.onReadResource) {
3739
+ throw new Error("No active session.");
3506
3740
  }
3507
3741
  const response = await this.fetchResourceWithCache(sessionId, uri);
3508
3742
  if (!response?.contents?.length) {
@@ -3513,10 +3747,18 @@ var AppHost = class {
3513
3747
  if (!html) {
3514
3748
  throw new Error(`Invalid content in resource: ${uri}`);
3515
3749
  }
3516
- const blob = new Blob([html], { type: "text/html" });
3517
- this.iframe.src = URL.createObjectURL(blob);
3750
+ return html;
3518
3751
  }
3519
3752
  async fetchResourceWithCache(sessionId, uri) {
3753
+ if (this.options.onReadResource) {
3754
+ return await this.options.onReadResource(uri);
3755
+ }
3756
+ if (!sessionId) {
3757
+ throw new Error("No active session");
3758
+ }
3759
+ if (!this.client) {
3760
+ throw new Error("No client to read resource from");
3761
+ }
3520
3762
  if (this.hasClientCache()) {
3521
3763
  return this.client.getOrFetchResource(sessionId, uri);
3522
3764
  }
@@ -3529,8 +3771,11 @@ var AppHost = class {
3529
3771
  }
3530
3772
  async preloadResource(uri) {
3531
3773
  try {
3774
+ if (this.options.onReadResource) {
3775
+ return await this.options.onReadResource(uri);
3776
+ }
3532
3777
  const sessionId = await this.getSessionId();
3533
- if (!sessionId) return null;
3778
+ if (!sessionId || !this.client) return null;
3534
3779
  return await this.client.readResource(sessionId, uri);
3535
3780
  } catch (error) {
3536
3781
  this.log(`Preload failed for ${uri}`, "warn");
@@ -3542,6 +3787,7 @@ var AppHost = class {
3542
3787
  // ============================================
3543
3788
  async getSessionId() {
3544
3789
  if (this.sessionId) return this.sessionId;
3790
+ if (!this.client) return void 0;
3545
3791
  const result = await this.client.getSessions();
3546
3792
  return result.sessions?.[0]?.sessionId;
3547
3793
  }
@@ -3549,6 +3795,7 @@ var AppHost = class {
3549
3795
  return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
3550
3796
  }
3551
3797
  hasClientCache() {
3798
+ if (!this.client) return false;
3552
3799
  return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
3553
3800
  }
3554
3801
  extractUiResourceUri(tool) {
@@ -3617,6 +3864,7 @@ function findToolByName(connections, toolName) {
3617
3864
  return void 0;
3618
3865
  }
3619
3866
 
3867
+ exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
3620
3868
  exports.AppHost = AppHost;
3621
3869
  exports.AuthenticationError = AuthenticationError;
3622
3870
  exports.ConfigurationError = ConfigurationError;
@@ -3625,6 +3873,7 @@ exports.DEFAULT_CLIENT_NAME = DEFAULT_CLIENT_NAME;
3625
3873
  exports.DEFAULT_CLIENT_URI = DEFAULT_CLIENT_URI;
3626
3874
  exports.DEFAULT_HEARTBEAT_INTERVAL_MS = DEFAULT_HEARTBEAT_INTERVAL_MS;
3627
3875
  exports.DEFAULT_LOGO_URI = DEFAULT_LOGO_URI;
3876
+ exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
3628
3877
  exports.DEFAULT_POLICY_URI = DEFAULT_POLICY_URI;
3629
3878
  exports.DisposableStore = DisposableStore;
3630
3879
  exports.Emitter = Emitter;
@@ -3637,6 +3886,8 @@ exports.MultiSessionClient = MultiSessionClient;
3637
3886
  exports.NotConnectedError = NotConnectedError;
3638
3887
  exports.REDIS_KEY_PREFIX = REDIS_KEY_PREFIX;
3639
3888
  exports.RpcErrorCodes = RpcErrorCodes;
3889
+ exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
3890
+ exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
3640
3891
  exports.SESSION_TTL_SECONDS = SESSION_TTL_SECONDS;
3641
3892
  exports.SOFTWARE_ID = SOFTWARE_ID;
3642
3893
  exports.SOFTWARE_VERSION = SOFTWARE_VERSION;