@mcp-ts/sdk 1.3.10 → 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 (44) hide show
  1. package/dist/adapters/langchain-adapter.js.map +1 -1
  2. package/dist/adapters/langchain-adapter.mjs.map +1 -1
  3. package/dist/client/index.d.mts +3 -189
  4. package/dist/client/index.d.ts +3 -189
  5. package/dist/client/index.js +218 -54
  6. package/dist/client/index.js.map +1 -1
  7. package/dist/client/index.mjs +215 -55
  8. package/dist/client/index.mjs.map +1 -1
  9. package/dist/client/react.d.mts +21 -14
  10. package/dist/client/react.d.ts +21 -14
  11. package/dist/client/react.js +402 -83
  12. package/dist/client/react.js.map +1 -1
  13. package/dist/client/react.mjs +400 -85
  14. package/dist/client/react.mjs.map +1 -1
  15. package/dist/client/vue.d.mts +3 -2
  16. package/dist/client/vue.d.ts +3 -2
  17. package/dist/client/vue.js +239 -63
  18. package/dist/client/vue.js.map +1 -1
  19. package/dist/client/vue.mjs +236 -64
  20. package/dist/client/vue.mjs.map +1 -1
  21. package/dist/index-CQr9q0bF.d.mts +295 -0
  22. package/dist/index-nE_7Io0I.d.ts +295 -0
  23. package/dist/index.d.mts +2 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.js +237 -58
  26. package/dist/index.js.map +1 -1
  27. package/dist/index.mjs +230 -59
  28. package/dist/index.mjs.map +1 -1
  29. package/dist/server/index.js +15 -4
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/server/index.mjs +15 -4
  32. package/dist/server/index.mjs.map +1 -1
  33. package/package.json +13 -11
  34. package/src/adapters/langchain-adapter.ts +1 -1
  35. package/src/client/core/app-host.ts +252 -65
  36. package/src/client/core/constants.ts +30 -0
  37. package/src/client/index.ts +6 -1
  38. package/src/client/react/index.ts +1 -0
  39. package/src/client/react/use-app-host.ts +8 -15
  40. package/src/client/react/use-mcp-apps.tsx +221 -26
  41. package/src/client/react/use-mcp.ts +23 -12
  42. package/src/client/utils/app-host-utils.ts +62 -0
  43. package/src/client/vue/use-mcp.ts +23 -12
  44. package/src/server/mcp/oauth-client.ts +31 -8
package/dist/index.js CHANGED
@@ -1794,13 +1794,24 @@ var MCPClient = class _MCPClient {
1794
1794
  }
1795
1795
  } catch (error) {
1796
1796
  if (error instanceof auth_js.UnauthorizedError || error instanceof Error && error.message.toLowerCase().includes("unauthorized")) {
1797
- this.emitStateChange("AUTHENTICATING");
1798
- console.log(`[MCPClient] Saving session ${this.sessionId} with 10min TTL (OAuth pending)`);
1799
- await this.saveSession(Math.floor(STATE_EXPIRATION_MS / 1e3), false);
1800
1797
  let authUrl = "";
1801
1798
  if (this.oauthProvider) {
1802
- authUrl = this.oauthProvider.authUrl || "";
1799
+ authUrl = (this.oauthProvider.authUrl || "").trim();
1803
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);
1804
1815
  if (this.serverId) {
1805
1816
  this._onConnectionEvent.fire({
1806
1817
  type: "auth_required",
@@ -3353,22 +3364,92 @@ var SSEClient = class {
3353
3364
 
3354
3365
  // src/client/core/app-host.ts
3355
3366
  init_cjs_shims();
3356
- var HOST_INFO = { name: "mcp-ts-host", version: "1.0.0" };
3357
- var SANDBOX_PERMISSIONS = [
3358
- "allow-scripts",
3359
- // Required for app JavaScript execution
3360
- "allow-forms",
3361
- // Required for form submissions
3362
- "allow-same-origin",
3363
- // Required for Blob URL correctness
3364
- "allow-modals",
3365
- // Required for dialogs/alerts
3366
- "allow-popups",
3367
- // Required for opening links
3368
- "allow-downloads"
3369
- // Required for file downloads
3370
- ].join(" ");
3371
- 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;
3372
3453
  var AppHost = class {
3373
3454
  constructor(client, iframe, options) {
3374
3455
  this.client = client;
@@ -3377,10 +3458,12 @@ var AppHost = class {
3377
3458
  __publicField(this, "sessionId");
3378
3459
  __publicField(this, "resourceCache", /* @__PURE__ */ new Map());
3379
3460
  __publicField(this, "debug");
3380
- /** Callback for app messages (e.g., chat messages from the app) */
3461
+ __publicField(this, "sandboxConfig");
3462
+ __publicField(this, "options");
3381
3463
  __publicField(this, "onAppMessage");
3382
- this.debug = options?.debug ?? false;
3383
- this.configureSandbox();
3464
+ this.options = options || {};
3465
+ this.debug = this.options.debug ?? false;
3466
+ this.sandboxConfig = this.options.sandbox;
3384
3467
  this.bridge = this.initializeBridge();
3385
3468
  }
3386
3469
  // ============================================
@@ -3407,19 +3490,35 @@ var AppHost = class {
3407
3490
  }
3408
3491
  }
3409
3492
  /**
3410
- * 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.
3411
3494
  * Loads the HTML first, then establishes bridge connection.
3412
3495
  */
3413
- async launch(url, sessionId) {
3496
+ async launch(source, sessionId) {
3414
3497
  if (sessionId) this.sessionId = sessionId;
3415
3498
  const initializedPromise = this.onAppReady();
3416
- if (this.isMcpUri(url)) {
3417
- await this.launchMcpApp(url);
3418
- } else {
3419
- 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
+ });
3420
3521
  }
3421
- await this.onIframeReady();
3422
- await this.connectBridge();
3423
3522
  this.log("Waiting for app initialization");
3424
3523
  await Promise.race([
3425
3524
  initializedPromise,
@@ -3430,6 +3529,19 @@ var AppHost = class {
3430
3529
  ]);
3431
3530
  this.log("App launched and ready");
3432
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
+ }
3433
3545
  /**
3434
3546
  * Wait for app to signal initialization complete
3435
3547
  */
@@ -3480,14 +3592,17 @@ var AppHost = class {
3480
3592
  this.log("Sending tool cancellation to app");
3481
3593
  this.bridge.sendToolCancelled({ reason });
3482
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
+ }
3483
3603
  // ============================================
3484
3604
  // Private: Initialization
3485
3605
  // ============================================
3486
- configureSandbox() {
3487
- if (this.iframe.sandbox.value !== SANDBOX_PERMISSIONS) {
3488
- this.iframe.sandbox.value = SANDBOX_PERMISSIONS;
3489
- }
3490
- }
3491
3606
  initializeBridge() {
3492
3607
  const bridge = new appBridge.AppBridge(
3493
3608
  null,
@@ -3496,12 +3611,10 @@ var AppHost = class {
3496
3611
  openLinks: {},
3497
3612
  serverTools: {},
3498
3613
  logging: {},
3499
- // Declare support for model context updates
3500
3614
  updateModelContext: { text: {} }
3501
3615
  },
3502
3616
  {
3503
- // Initial host context
3504
- hostContext: {
3617
+ hostContext: this.options.hostContext || {
3505
3618
  theme: "dark",
3506
3619
  platform: "web",
3507
3620
  containerDimensions: { maxHeight: 6e3 },
@@ -3510,19 +3623,56 @@ var AppHost = class {
3510
3623
  }
3511
3624
  }
3512
3625
  );
3626
+ bridge.fallbackRequestHandler = this.options.onFallbackRequest;
3513
3627
  bridge.oncalltool = (params) => this.handleToolCall(params);
3514
- bridge.onopenlink = this.handleOpenLink.bind(this);
3515
- bridge.onmessage = this.handleMessage.bind(this);
3516
- 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
+ };
3517
3658
  bridge.onupdatemodelcontext = async () => ({});
3518
- bridge.onsizechange = async ({ width, height }) => {
3519
- if (height !== void 0) this.iframe.style.height = `${height}px`;
3520
- 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
+ }
3521
3668
  return {};
3522
3669
  };
3523
- bridge.onrequestdisplaymode = async (params) => ({
3524
- mode: params.mode === "fullscreen" ? "fullscreen" : "inline"
3525
- });
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
+ };
3526
3676
  return bridge;
3527
3677
  }
3528
3678
  async connectBridge() {
@@ -3536,6 +3686,9 @@ var AppHost = class {
3536
3686
  this.log("Bridge connected successfully");
3537
3687
  } catch (error) {
3538
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
+ }
3539
3692
  throw error;
3540
3693
  }
3541
3694
  }
@@ -3543,8 +3696,11 @@ var AppHost = class {
3543
3696
  // Private: Bridge Event Handlers
3544
3697
  // ============================================
3545
3698
  async handleToolCall(params) {
3546
- if (!this.client.isConnected()) {
3547
- 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");
3548
3704
  }
3549
3705
  const sessionId = await this.getSessionId();
3550
3706
  if (!sessionId) {
@@ -3568,13 +3724,19 @@ var AppHost = class {
3568
3724
  // ============================================
3569
3725
  // Private: Resource Loading
3570
3726
  // ============================================
3571
- async launchMcpApp(uri) {
3572
- if (!this.client.isConnected()) {
3573
- 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));
3574
3732
  }
3733
+ const { onReady } = await setupSandboxProxyIframe(this.iframe, url);
3734
+ await onReady;
3735
+ }
3736
+ async readMcpAppHtml(uri) {
3575
3737
  const sessionId = await this.getSessionId();
3576
- if (!sessionId) {
3577
- throw new Error("No active session");
3738
+ if (!sessionId && !this.options.onReadResource) {
3739
+ throw new Error("No active session.");
3578
3740
  }
3579
3741
  const response = await this.fetchResourceWithCache(sessionId, uri);
3580
3742
  if (!response?.contents?.length) {
@@ -3585,10 +3747,18 @@ var AppHost = class {
3585
3747
  if (!html) {
3586
3748
  throw new Error(`Invalid content in resource: ${uri}`);
3587
3749
  }
3588
- const blob = new Blob([html], { type: "text/html" });
3589
- this.iframe.src = URL.createObjectURL(blob);
3750
+ return html;
3590
3751
  }
3591
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
+ }
3592
3762
  if (this.hasClientCache()) {
3593
3763
  return this.client.getOrFetchResource(sessionId, uri);
3594
3764
  }
@@ -3601,8 +3771,11 @@ var AppHost = class {
3601
3771
  }
3602
3772
  async preloadResource(uri) {
3603
3773
  try {
3774
+ if (this.options.onReadResource) {
3775
+ return await this.options.onReadResource(uri);
3776
+ }
3604
3777
  const sessionId = await this.getSessionId();
3605
- if (!sessionId) return null;
3778
+ if (!sessionId || !this.client) return null;
3606
3779
  return await this.client.readResource(sessionId, uri);
3607
3780
  } catch (error) {
3608
3781
  this.log(`Preload failed for ${uri}`, "warn");
@@ -3614,6 +3787,7 @@ var AppHost = class {
3614
3787
  // ============================================
3615
3788
  async getSessionId() {
3616
3789
  if (this.sessionId) return this.sessionId;
3790
+ if (!this.client) return void 0;
3617
3791
  const result = await this.client.getSessions();
3618
3792
  return result.sessions?.[0]?.sessionId;
3619
3793
  }
@@ -3621,6 +3795,7 @@ var AppHost = class {
3621
3795
  return MCP_URI_SCHEMES.some((scheme) => url.startsWith(scheme));
3622
3796
  }
3623
3797
  hasClientCache() {
3798
+ if (!this.client) return false;
3624
3799
  return "getOrFetchResource" in this.client && typeof this.client.getOrFetchResource === "function";
3625
3800
  }
3626
3801
  extractUiResourceUri(tool) {
@@ -3689,6 +3864,7 @@ function findToolByName(connections, toolName) {
3689
3864
  return void 0;
3690
3865
  }
3691
3866
 
3867
+ exports.APP_HOST_DEFAULTS = APP_HOST_DEFAULTS;
3692
3868
  exports.AppHost = AppHost;
3693
3869
  exports.AuthenticationError = AuthenticationError;
3694
3870
  exports.ConfigurationError = ConfigurationError;
@@ -3697,6 +3873,7 @@ exports.DEFAULT_CLIENT_NAME = DEFAULT_CLIENT_NAME;
3697
3873
  exports.DEFAULT_CLIENT_URI = DEFAULT_CLIENT_URI;
3698
3874
  exports.DEFAULT_HEARTBEAT_INTERVAL_MS = DEFAULT_HEARTBEAT_INTERVAL_MS;
3699
3875
  exports.DEFAULT_LOGO_URI = DEFAULT_LOGO_URI;
3876
+ exports.DEFAULT_MCP_APP_CSP = DEFAULT_MCP_APP_CSP;
3700
3877
  exports.DEFAULT_POLICY_URI = DEFAULT_POLICY_URI;
3701
3878
  exports.DisposableStore = DisposableStore;
3702
3879
  exports.Emitter = Emitter;
@@ -3709,6 +3886,8 @@ exports.MultiSessionClient = MultiSessionClient;
3709
3886
  exports.NotConnectedError = NotConnectedError;
3710
3887
  exports.REDIS_KEY_PREFIX = REDIS_KEY_PREFIX;
3711
3888
  exports.RpcErrorCodes = RpcErrorCodes;
3889
+ exports.SANDBOX_PROXY_READY_METHOD = SANDBOX_PROXY_READY_METHOD;
3890
+ exports.SANDBOX_RESOURCE_READY_METHOD = SANDBOX_RESOURCE_READY_METHOD;
3712
3891
  exports.SESSION_TTL_SECONDS = SESSION_TTL_SECONDS;
3713
3892
  exports.SOFTWARE_ID = SOFTWARE_ID;
3714
3893
  exports.SOFTWARE_VERSION = SOFTWARE_VERSION;