@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.mjs CHANGED
@@ -7,6 +7,7 @@ import { ListToolsResultSchema, CallToolResultSchema, ListPromptsResultSchema, G
7
7
  import * as fs2 from 'fs';
8
8
  import { promises } from 'fs';
9
9
  import * as path from 'path';
10
+ import { createDecipheriv, randomBytes, createCipheriv } from 'crypto';
10
11
  import { AppBridge, PostMessageTransport } from '@modelcontextprotocol/ext-apps/app-bridge';
11
12
 
12
13
  var __defProp = Object.defineProperty;
@@ -710,6 +711,72 @@ var SqliteStorage = class {
710
711
  }
711
712
  }
712
713
  };
714
+ var ALGORITHM = "aes-256-gcm";
715
+ var IV_LENGTH = 12;
716
+ var ENCRYPTION_PREFIX = "enc:1:";
717
+ var warningLogged = false;
718
+ function getKey() {
719
+ const keyString = process.env.STORAGE_ENCRYPTION_KEY;
720
+ if (!keyString) return null;
721
+ if (keyString.length === 64) {
722
+ return Buffer.from(keyString, "hex");
723
+ } else {
724
+ const keyBuffer = Buffer.alloc(32);
725
+ keyBuffer.write(keyString, 0, 32, "utf-8");
726
+ return keyBuffer;
727
+ }
728
+ }
729
+ function encryptObject(data) {
730
+ if (data === void 0 || data === null) return data;
731
+ const key = getKey();
732
+ if (!key) {
733
+ if (!warningLogged) {
734
+ console.warn("[mcp-ts][Storage] WARNING: STORAGE_ENCRYPTION_KEY is not set. Saving sensitive data in plain-text.");
735
+ warningLogged = true;
736
+ }
737
+ return data;
738
+ }
739
+ try {
740
+ const text = JSON.stringify(data);
741
+ const iv = randomBytes(IV_LENGTH);
742
+ const cipher = createCipheriv(ALGORITHM, key, iv);
743
+ let encrypted = cipher.update(text, "utf-8", "hex");
744
+ encrypted += cipher.final("hex");
745
+ const authTag = cipher.getAuthTag().toString("hex");
746
+ return `${ENCRYPTION_PREFIX}${iv.toString("hex")}:${authTag}:${encrypted}`;
747
+ } catch (e) {
748
+ console.error("[mcp-ts][Storage] Encryption failed, falling back to plain-text.", e);
749
+ return data;
750
+ }
751
+ }
752
+ function decryptObject(data) {
753
+ if (data === void 0 || data === null) return data;
754
+ if (typeof data !== "string" || !data.startsWith(ENCRYPTION_PREFIX)) {
755
+ return data;
756
+ }
757
+ const key = getKey();
758
+ if (!key) {
759
+ console.warn("[mcp-ts][Storage] WARNING: Found encrypted data but STORAGE_ENCRYPTION_KEY is missing. Returning raw encrypted string.");
760
+ return data;
761
+ }
762
+ try {
763
+ const parts = data.split(":");
764
+ if (parts.length !== 5) {
765
+ return data;
766
+ }
767
+ const iv = Buffer.from(parts[2], "hex");
768
+ const authTag = Buffer.from(parts[3], "hex");
769
+ const encryptedText = parts[4];
770
+ const decipher = createDecipheriv(ALGORITHM, key, iv);
771
+ decipher.setAuthTag(authTag);
772
+ let decrypted = decipher.update(encryptedText, "hex", "utf-8");
773
+ decrypted += decipher.final("utf-8");
774
+ return JSON.parse(decrypted);
775
+ } catch (e) {
776
+ console.error("[mcp-ts][Storage] Decryption failed.", e);
777
+ return data;
778
+ }
779
+ }
713
780
 
714
781
  // src/server/storage/supabase-backend.ts
715
782
  var SupabaseStorageBackend = class {
@@ -742,10 +809,10 @@ var SupabaseStorageBackend = class {
742
809
  callbackUrl: row.callback_url,
743
810
  createdAt: new Date(row.created_at).getTime(),
744
811
  identity: row.identity,
745
- headers: row.headers,
812
+ headers: decryptObject(row.headers),
746
813
  active: row.active,
747
814
  clientInformation: row.client_information,
748
- tokens: row.tokens,
815
+ tokens: decryptObject(row.tokens),
749
816
  codeVerifier: row.code_verifier,
750
817
  clientId: row.client_id
751
818
  };
@@ -766,10 +833,10 @@ var SupabaseStorageBackend = class {
766
833
  callback_url: session.callbackUrl,
767
834
  created_at: new Date(session.createdAt || Date.now()).toISOString(),
768
835
  identity,
769
- headers: session.headers,
836
+ headers: encryptObject(session.headers),
770
837
  active: session.active ?? false,
771
838
  client_information: session.clientInformation,
772
- tokens: session.tokens,
839
+ tokens: encryptObject(session.tokens),
773
840
  code_verifier: session.codeVerifier,
774
841
  client_id: session.clientId,
775
842
  expires_at: expiresAt
@@ -794,9 +861,9 @@ var SupabaseStorageBackend = class {
794
861
  if ("transportType" in data) updateData.transport_type = data.transportType;
795
862
  if ("callbackUrl" in data) updateData.callback_url = data.callbackUrl;
796
863
  if ("active" in data) updateData.active = data.active;
797
- if ("headers" in data) updateData.headers = data.headers;
864
+ if ("headers" in data) updateData.headers = encryptObject(data.headers);
798
865
  if ("clientInformation" in data) updateData.client_information = data.clientInformation;
799
- if ("tokens" in data) updateData.tokens = data.tokens;
866
+ if ("tokens" in data) updateData.tokens = encryptObject(data.tokens);
800
867
  if ("codeVerifier" in data) updateData.code_verifier = data.codeVerifier;
801
868
  if ("clientId" in data) updateData.client_id = data.clientId;
802
869
  const { data: updatedRows, error } = await this.supabase.from("mcp_sessions").update(updateData).eq("identity", identity).eq("session_id", sessionId).select("id");
@@ -1658,13 +1725,24 @@ var MCPClient = class _MCPClient {
1658
1725
  }
1659
1726
  } catch (error) {
1660
1727
  if (error instanceof UnauthorizedError$1 || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
1661
- this.emitStateChange("AUTHENTICATING");
1662
- console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1663
- await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1664
1728
  let authUrl = "";
1665
1729
  if (this.oauthProvider) {
1666
- authUrl = this.oauthProvider.authUrl || "";
1730
+ authUrl = (this.oauthProvider.authUrl || "").trim();
1731
+ }
1732
+ if (!authUrl) {
1733
+ const detail = error instanceof Error && error.message.trim().length > 0 ? error.message.trim() : "Unauthorized";
1734
+ const message = detail.toLowerCase() === "unauthorized" ? "OAuth authorization URL not available" : `OAuth authorization URL not available: ${detail}`;
1735
+ this.emitError(message, "auth");
1736
+ this.emitStateChange("FAILED");
1737
+ try {
1738
+ await storage.removeSession(this.identity, this.sessionId);
1739
+ } catch {
1740
+ }
1741
+ throw new Error(message);
1667
1742
  }
1743
+ this.emitStateChange("AUTHENTICATING");
1744
+ console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1745
+ await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1668
1746
  if (this.serverId) {
1669
1747
  this._onConnectionEvent.fire({
1670
1748
  type: "auth_required",
@@ -3202,22 +3280,88 @@ var SSEClient = class {
3202
3280
  }
3203
3281
  }
3204
3282
  };
3205
- var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
3206
- var SANDBOX_PERMISSIONS = [
3207
- "allow-scripts",
3208
- // Required for app JavaScript execution
3209
- "allow-forms",
3210
- // Required for form submissions
3211
- "allow-same-origin",
3212
- // Required for Blob URL correctness
3213
- "allow-modals",
3214
- // Required for dialogs/alerts
3215
- "allow-popups",
3216
- // Required for opening links
3217
- "allow-downloads"
3218
- // Required for file downloads
3219
- ].join(" ");
3220
- var MCP_URI_SCHEMES = ["ui://", "mcp-app://"];
3283
+
3284
+ // src/client/core/constants.ts
3285
+ var SANDBOX_PROXY_READY_METHOD = "ui/notifications/sandbox-proxy-ready";
3286
+ var SANDBOX_RESOURCE_READY_METHOD = "ui/notifications/sandbox-resource-ready";
3287
+ var APP_HOST_DEFAULTS = {
3288
+ /** Default timeout for waiting for the sandbox proxy to be ready (ms). */
3289
+ SANDBOX_TIMEOUT_MS: 1e4,
3290
+ /** Default host info reported to guest apps. */
3291
+ HOST_INFO: { name: "mcp-ts-host", version: "1.0.0" },
3292
+ /** Supported MCP App URI schemes. */
3293
+ URI_SCHEMES: ["ui://", "mcp-app://"],
3294
+ /** Default theme for the host context. */
3295
+ THEME: "dark",
3296
+ /** Default platform for the host context. */
3297
+ PLATFORM: "web",
3298
+ /** Default max height for the iframe container (px). */
3299
+ MAX_HEIGHT: 6e3
3300
+ };
3301
+
3302
+ // src/client/utils/app-host-utils.ts
3303
+ var DEFAULT_SANDBOX_TIMEOUT_MS = APP_HOST_DEFAULTS.SANDBOX_TIMEOUT_MS;
3304
+ async function setupSandboxProxyIframe(iframe, sandboxProxyUrl) {
3305
+ iframe.style.width = "100%";
3306
+ iframe.style.height = "100%";
3307
+ iframe.style.border = "none";
3308
+ iframe.style.backgroundColor = "transparent";
3309
+ iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
3310
+ const onReady = new Promise((resolve, reject) => {
3311
+ let settled = false;
3312
+ const cleanup = () => {
3313
+ window.removeEventListener("message", messageListener);
3314
+ iframe.removeEventListener("error", errorListener);
3315
+ };
3316
+ const timeoutId = setTimeout(() => {
3317
+ if (!settled) {
3318
+ settled = true;
3319
+ cleanup();
3320
+ reject(new Error("Timed out waiting for sandbox proxy iframe to be ready"));
3321
+ }
3322
+ }, DEFAULT_SANDBOX_TIMEOUT_MS);
3323
+ const messageListener = (event) => {
3324
+ if (event.source === iframe.contentWindow) {
3325
+ if (event.data?.method === SANDBOX_PROXY_READY_METHOD) {
3326
+ if (!settled) {
3327
+ settled = true;
3328
+ clearTimeout(timeoutId);
3329
+ cleanup();
3330
+ resolve();
3331
+ }
3332
+ }
3333
+ }
3334
+ };
3335
+ const errorListener = () => {
3336
+ if (!settled) {
3337
+ settled = true;
3338
+ clearTimeout(timeoutId);
3339
+ cleanup();
3340
+ reject(new Error("Failed to load sandbox proxy iframe"));
3341
+ }
3342
+ };
3343
+ window.addEventListener("message", messageListener);
3344
+ iframe.addEventListener("error", errorListener);
3345
+ });
3346
+ iframe.src = sandboxProxyUrl.href;
3347
+ return { onReady };
3348
+ }
3349
+
3350
+ // src/client/core/app-host.ts
3351
+ var DEFAULT_MCP_APP_CSP = {
3352
+ "default-src": "'self'",
3353
+ "script-src": "'self' 'unsafe-inline' 'unsafe-eval' https: blob:",
3354
+ "style-src": "'self' 'unsafe-inline' https:",
3355
+ "connect-src": "'self' https: wss:",
3356
+ "img-src": "'self' data: https: blob:",
3357
+ "font-src": "'self' data: https:",
3358
+ "media-src": "'self' https: blob:",
3359
+ "frame-src": "'none'",
3360
+ "object-src": "'none'",
3361
+ "base-uri": "'self'"
3362
+ };
3363
+ var HOST_INFO = APP_HOST_DEFAULTS.HOST_INFO;
3364
+ var MCP_URI_SCHEMES = APP_HOST_DEFAULTS.URI_SCHEMES;
3221
3365
  var AppHost = class {
3222
3366
  constructor(client, iframe, options) {
3223
3367
  this.client = client;
@@ -3226,10 +3370,12 @@ var AppHost = class {
3226
3370
  __publicField(this, "sessionId");
3227
3371
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
3228
3372
  __publicField(this, "debug");
3229
- /** Callback for app messages (e.g., chat messages from the app) */
3373
+ __publicField(this, "sandboxConfig");
3374
+ __publicField(this, "options");
3230
3375
  __publicField(this, "onAppMessage");
3231
- this.debug = options?.debug ?? false;
3232
- this.configureSandbox();
3376
+ this.options = options || {};
3377
+ this.debug = this.options.debug ?? false;
3378
+ this.sandboxConfig = this.options.sandbox;
3233
3379
  this.bridge = this.initializeBridge();
3234
3380
  }
3235
3381
  // ============================================
@@ -3256,19 +3402,35 @@ var AppHost = class {
3256
3402
  }
3257
3403
  }
3258
3404
  /**
3259
- * Launch an MCP App from a URL or MCP resource URI.
3405
+ * Launch an MCP App from a URL, MCP resource URI, or RAW HTML.
3260
3406
  * Loads the HTML first, then establishes bridge connection.
3261
3407
  */
3262
- async launch(url, sessionId) {
3408
+ async launch(source, sessionId) {
3263
3409
  if (sessionId) this.sessionId = sessionId;
3264
3410
  const initializedPromise = this.onAppReady();
3265
- if (this.isMcpUri(url)) {
3266
- await this.launchMcpApp(url);
3267
- } else {
3268
- this.iframe.src = url;
3411
+ let htmlToRender = source.html;
3412
+ if (!htmlToRender && source.uri) {
3413
+ if (this.isMcpUri(source.uri)) {
3414
+ htmlToRender = await this.readMcpAppHtml(source.uri);
3415
+ }
3416
+ }
3417
+ if (!htmlToRender && source.uri && !this.isMcpUri(source.uri)) {
3418
+ this.iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms allow-modals allow-popups allow-downloads");
3419
+ this.iframe.src = source.uri;
3420
+ await this.onIframeReady();
3421
+ await this.connectBridge();
3422
+ } else if (htmlToRender) {
3423
+ if (!this.sandboxConfig) {
3424
+ throw new Error("Sandbox configuration requires a proxy URL to render HTML safely.");
3425
+ }
3426
+ await this.launchSandboxedHtml(htmlToRender, this.sandboxConfig);
3427
+ await this.connectBridge();
3428
+ this.log("Sending HTML resource to sandbox proxy (MCP Apps notification)");
3429
+ await this.bridge.sendSandboxResourceReady({
3430
+ html: htmlToRender,
3431
+ csp: this.sandboxConfig.csp
3432
+ });
3269
3433
  }
3270
- await this.onIframeReady();
3271
- await this.connectBridge();
3272
3434
  this.log("Waiting for app initialization");
3273
3435
  await Promise.race([
3274
3436
  initializedPromise,
@@ -3279,6 +3441,19 @@ var AppHost = class {
3279
3441
  ]);
3280
3442
  this.log("App launched and ready");
3281
3443
  }
3444
+ // Set host context manually
3445
+ setHostContext(context) {
3446
+ this.options.hostContext = context;
3447
+ if (this.bridge) {
3448
+ this.bridge.setHostContext(context);
3449
+ }
3450
+ }
3451
+ // Send streaming inputs manually
3452
+ sendToolInputPartial(params) {
3453
+ if (this.bridge) {
3454
+ this.bridge.sendToolInputPartial(params);
3455
+ }
3456
+ }
3282
3457
  /**
3283
3458
  * Wait for app to signal initialization complete
3284
3459
  */
@@ -3329,14 +3504,17 @@ var AppHost = class {
3329
3504
  this.log("Sending tool cancellation to app");
3330
3505
  this.bridge.sendToolCancelled({ reason });
3331
3506
  }
3507
+ /**
3508
+ * Tell the guest UI the resource is being torn down (unload / cleanup).
3509
+ * Forwards to {@link AppBridge.teardownResource} on `@modelcontextprotocol/ext-apps/app-bridge`.
3510
+ */
3511
+ teardownResource(params = {}) {
3512
+ this.log("Sending resource teardown to app");
3513
+ this.bridge.teardownResource(params);
3514
+ }
3332
3515
  // ============================================
3333
3516
  // Private: Initialization
3334
3517
  // ============================================
3335
- configureSandbox() {
3336
- if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
3337
- this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
3338
- }
3339
- }
3340
3518
  initializeBridge() {
3341
3519
  const bridge = new AppBridge(
3342
3520
  null,
@@ -3345,12 +3523,10 @@ var AppHost = class {
3345
3523
  openLinks: {},
3346
3524
  serverTools: {},
3347
3525
  logging: {},
3348
- // Declare support for model context updates
3349
3526
  updateModelContext: { text: {} }
3350
3527
  },
3351
3528
  {
3352
- // Initial host context
3353
- hostContext: {
3529
+ hostContext: this.options.hostContext || {
3354
3530
  theme: "dark",
3355
3531
  platform: "web",
3356
3532
  containerDimensions: { maxHeight: 6e3 },
@@ -3359,19 +3535,56 @@ var AppHost = class {
3359
3535
  }
3360
3536
  }
3361
3537
  );
3538
+ bridge.fallbackRequestHandler = this.options.onFallbackRequest;
3362
3539
  bridge.oncalltool = (params) => this.handleToolCall(params);
3363
- bridge.onopenlink = this.handleOpenLink.bind(this);
3364
- bridge.onmessage = this.handleMessage.bind(this);
3365
- bridge.onloggingmessage = (params) => this.log(`App log [${params.level}]: ${params.data}`);
3540
+ if (this.options.onReadResource) {
3541
+ bridge.onreadresource = async (params) => {
3542
+ const resp = await this.options.onReadResource(params.uri);
3543
+ return {
3544
+ contents: resp.contents.map((c) => ({
3545
+ uri: params.uri,
3546
+ text: c.text,
3547
+ blob: c.blob
3548
+ }))
3549
+ };
3550
+ };
3551
+ }
3552
+ bridge.onopenlink = async (params, extra) => {
3553
+ if (this.options.onOpenLink) {
3554
+ return await this.options.onOpenLink(params, extra);
3555
+ }
3556
+ return this.handleOpenLink(params);
3557
+ };
3558
+ bridge.onmessage = async (params, extra) => {
3559
+ if (this.options.onMessage) {
3560
+ return await this.options.onMessage(params, extra);
3561
+ }
3562
+ return this.handleMessage(params);
3563
+ };
3564
+ bridge.onloggingmessage = (params) => {
3565
+ this.log(`App log [${params.level}]: ${params.data}`);
3566
+ if (this.options.onLoggingMessage) {
3567
+ this.options.onLoggingMessage(params);
3568
+ }
3569
+ };
3366
3570
  bridge.onupdatemodelcontext = async () => ({});
3367
- bridge.onsizechange = async ({ width, height }) => {
3368
- if (height !== void 0) this.iframe.style.height = `${height}px`;
3369
- if (width !== void 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
3571
+ bridge.onsizechange = async (params) => {
3572
+ const { width, height } = params;
3573
+ if (height !== void 0 && height > 0) {
3574
+ this.iframe.style.height = `${height}px`;
3575
+ }
3576
+ if (width !== void 0 && width > 0) this.iframe.style.minWidth = `min(${width}px, 100%)`;
3577
+ if (this.options.onSizeChanged) {
3578
+ this.options.onSizeChanged(params);
3579
+ }
3370
3580
  return {};
3371
3581
  };
3372
- bridge.onrequestdisplaymode = async (params) => ({
3373
- mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
3374
- });
3582
+ bridge.onrequestdisplaymode = async (params, extra) => {
3583
+ if (this.options.onRequestDisplayMode) {
3584
+ return await this.options.onRequestDisplayMode(params, extra);
3585
+ }
3586
+ return { mode: params.mode === "fullscreen" ? "fullscreen" : "inline" };
3587
+ };
3375
3588
  return bridge;
3376
3589
  }
3377
3590
  async connectBridge() {
@@ -3385,6 +3598,9 @@ var AppHost = class {
3385
3598
  this.log("Bridge connected successfully");
3386
3599
  } catch (error) {
3387
3600
  this.log("Bridge connection failed", "error");
3601
+ if (this.options.onError) {
3602
+ this.options.onError(error instanceof Error ? error : new Error(String(error)));
3603
+ }
3388
3604
  throw error;
3389
3605
  }
3390
3606
  }
@@ -3392,8 +3608,11 @@ var AppHost = class {
3392
3608
  // Private: Bridge Event Handlers
3393
3609
  // ============================================
3394
3610
  async handleToolCall(params) {
3395
- if (!this.client.isConnected()) {
3396
- throw new Error("Client disconnected");
3611
+ if (this.options.onCallTool) {
3612
+ return await this.options.onCallTool(params);
3613
+ }
3614
+ if (!this.client || !this.client.isConnected()) {
3615
+ throw new Error("Client disconnected or not provided");
3397
3616
  }
3398
3617
  const sessionId = await this.getSessionId();
3399
3618
  if (!sessionId) {
@@ -3417,13 +3636,19 @@ var AppHost = class {
3417
3636
  // ============================================
3418
3637
  // Private: Resource Loading
3419
3638
  // ============================================
3420
- async launchMcpApp(uri) {
3421
- if (!this.client.isConnected()) {
3422
- throw new Error("Client must be connected");
3639
+ async launchSandboxedHtml(html, config) {
3640
+ const sandboxUrlString = config.url instanceof URL ? config.url.href : config.url;
3641
+ const url = new URL(sandboxUrlString, globalThis.location?.href);
3642
+ if (config.csp && Object.keys(config.csp).length > 0) {
3643
+ url.searchParams.set("csp", JSON.stringify(config.csp));
3423
3644
  }
3645
+ const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
3646
+ await onReady;
3647
+ }
3648
+ async readMcpAppHtml(uri) {
3424
3649
  const sessionId = await this.getSessionId();
3425
- if (!sessionId) {
3426
- throw new Error("No active session");
3650
+ if (!sessionId && !this.options.onReadResource) {
3651
+ throw new Error("No active session.");
3427
3652
  }
3428
3653
  const response = await this.fetchResourceWithCache(sessionId, uri);
3429
3654
  if (!response?.contents?.length) {
@@ -3434,10 +3659,18 @@ var AppHost = class {
3434
3659
  if (!html) {
3435
3660
  throw new Error(`Invalid content in resource: ${uri}`);
3436
3661
  }
3437
- const blob = new Blob([html], { type: "text/html" });
3438
- this.iframe.src = URL.createObjectURL(blob);
3662
+ return html;
3439
3663
  }
3440
3664
  async fetchResourceWithCache(sessionId, uri) {
3665
+ if (this.options.onReadResource) {
3666
+ return await this.options.onReadResource(uri);
3667
+ }
3668
+ if (!sessionId) {
3669
+ throw new Error("No active session");
3670
+ }
3671
+ if (!this.client) {
3672
+ throw new Error("No client to read resource from");
3673
+ }
3441
3674
  if (this.hasClientCache()) {
3442
3675
  return this.client.getOrFetchResource(sessionId, uri);
3443
3676
  }
@@ -3450,8 +3683,11 @@ var AppHost = class {
3450
3683
  }
3451
3684
  async preloadResource(uri) {
3452
3685
  try {
3686
+ if (this.options.onReadResource) {
3687
+ return await this.options.onReadResource(uri);
3688
+ }
3453
3689
  const sessionId = await this.getSessionId();
3454
- if (!sessionId) return null;
3690
+ if (!sessionId || !this.client) return null;
3455
3691
  return await this.client.readResource(sessionId, uri);
3456
3692
  } catch (error) {
3457
3693
  this.log(`Preload failed for ${uri}`, "warn");
@@ -3463,6 +3699,7 @@ var AppHost = class {
3463
3699
  // ============================================
3464
3700
  async getSessionId() {
3465
3701
  if (this.sessionId) return this.sessionId;
3702
+ if (!this.client) return void 0;
3466
3703
  const result = await this.client.getSessions();
3467
3704
  return result.sessions?.[0]?.sessionId;
3468
3705
  }
@@ -3470,6 +3707,7 @@ var AppHost = class {
3470
3707
  return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
3471
3708
  }
3472
3709
  hasClientCache() {
3710
+ if (!this.client) return false;
3473
3711
  return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
3474
3712
  }
3475
3713
  extractUiResourceUri(tool) {
@@ -3533,6 +3771,6 @@ function findToolByName(connections, toolName) {
3533
3771
  return void 0;
3534
3772
  }
3535
3773
 
3536
- export { AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SESSION_TTL_SECONDS, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, TOKEN_EXPIRY_BUFFER_MS, ToolExecutionError, UnauthorizedError, createNextMcpHandler, createSSEHandler, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, sanitizeServerLabel, storage };
3774
+ export { APP_HOST_DEFAULTS, AppHost, AuthenticationError, ConfigurationError, ConnectionError, DEFAULT_CLIENT_NAME, DEFAULT_CLIENT_URI, DEFAULT_HEARTBEAT_INTERVAL_MS, DEFAULT_LOGO_URI, DEFAULT_MCP_APP_CSP, DEFAULT_POLICY_URI, DisposableStore, Emitter, InvalidStateError, MCPClient, MCP_CLIENT_NAME, MCP_CLIENT_VERSION, McpError, MultiSessionClient, NotConnectedError, REDIS_KEY_PREFIX, RpcErrorCodes, SANDBOX_PROXY_READY_METHOD, SANDBOX_RESOURCE_READY_METHOD, SESSION_TTL_SECONDS, SOFTWARE_ID, SOFTWARE_VERSION, SSEClient, SSEConnectionManager, STATE_EXPIRATION_MS, SessionNotFoundError, SessionValidationError, StorageOAuthClientProvider, TOKEN_EXPIRY_BUFFER_MS, ToolExecutionError, UnauthorizedError, createNextMcpHandler, createSSEHandler, findToolByName, getToolUiResourceUri, isCallToolSuccess, isConnectAuthRequired, isConnectError, isConnectSuccess, isListToolsSuccess, sanitizeServerLabel, storage };
3537
3775
  //# sourceMappingURL=index.mjs.map
3538
3776
  //# sourceMappingURL=index.mjs.map