@mcp-use/client 2.0.0-beta.13 → 2.0.0-beta.15

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 (37) hide show
  1. package/README.md +1 -1
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/auth/browser.d.ts +3 -0
  4. package/dist/auth/browser.d.ts.map +1 -1
  5. package/dist/auth/callback.d.ts.map +1 -1
  6. package/dist/auth/flow.d.ts.map +1 -1
  7. package/dist/auth/node.d.ts +16 -1
  8. package/dist/auth/node.d.ts.map +1 -1
  9. package/dist/index-browser.js +124 -64
  10. package/dist/index-browser.js.map +1 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +137 -47
  14. package/dist/index.js.map +1 -1
  15. package/dist/react/index.d.ts +1 -1
  16. package/dist/react/index.d.ts.map +1 -1
  17. package/dist/react/index.js +424 -152
  18. package/dist/react/index.js.map +1 -1
  19. package/dist/react/storage.d.ts.map +1 -1
  20. package/dist/react/token-expiry.d.ts +9 -0
  21. package/dist/react/token-expiry.d.ts.map +1 -0
  22. package/dist/react/useMcp.d.ts.map +1 -1
  23. package/dist/react/view/ViewRenderer.d.ts +5 -4
  24. package/dist/react/view/ViewRenderer.d.ts.map +1 -1
  25. package/dist/react/view/ext-apps-bridge.d.ts +1 -1
  26. package/dist/react/view/ext-apps-bridge.d.ts.map +1 -1
  27. package/dist/react/view/sandbox-blob-url.d.ts +2 -0
  28. package/dist/react/view/sandbox-blob-url.d.ts.map +1 -1
  29. package/dist/react/view/types.d.ts +22 -4
  30. package/dist/react/view/types.d.ts.map +1 -1
  31. package/dist/react/view/view-host-policy.d.ts +26 -0
  32. package/dist/react/view/view-host-policy.d.ts.map +1 -0
  33. package/dist/telemetry/events.d.ts.map +1 -1
  34. package/dist/telemetry/tel-fetch.d.ts.map +1 -1
  35. package/dist/transport/connection-manager.d.ts +4 -0
  36. package/dist/transport/connection-manager.d.ts.map +1 -1
  37. package/package.json +1 -1
@@ -1534,7 +1534,7 @@ var HttpConnector = class extends BaseConnector {
1534
1534
  };
1535
1535
 
1536
1536
  // src/utils/version.ts
1537
- var VERSION = "2.0.0-beta.13";
1537
+ var VERSION = "2.0.0-beta.15";
1538
1538
  function getPackageVersion() {
1539
1539
  return VERSION;
1540
1540
  }
@@ -2068,7 +2068,7 @@ var OAuthSessionStore = class _OAuthSessionStore {
2068
2068
  */
2069
2069
  async storeAuthorizationState(authorizationUrl, opts = {}) {
2070
2070
  const state = globalThis.crypto.randomUUID();
2071
- const stateKey = `${this.storageKeyPrefix}:state_${state}`;
2071
+ const stateKey = `${this.storageKeyPrefix}_${this.serverUrlHash}_state_${state}`;
2072
2072
  const stateData = {
2073
2073
  serverUrlHash: this.serverUrlHash,
2074
2074
  expiry: Date.now() + 1e3 * 60 * 10,
@@ -2140,6 +2140,7 @@ var BrowserOAuthClientProvider = class {
2140
2140
  connectionUrl;
2141
2141
  proxyOAuthRequests;
2142
2142
  lastAttemptedAuthUrl = null;
2143
+ authorizationPending = false;
2143
2144
  onPopupWindow;
2144
2145
  constructor(serverUrl, options = {}) {
2145
2146
  if (options.staticClientInfo?.client_secret) {
@@ -2190,6 +2191,12 @@ var BrowserOAuthClientProvider = class {
2190
2191
  getKey(keySuffix) {
2191
2192
  return this.session.getKey(keySuffix);
2192
2193
  }
2194
+ get hasPendingFlow() {
2195
+ return this.authorizationPending;
2196
+ }
2197
+ markFlowComplete() {
2198
+ this.authorizationPending = false;
2199
+ }
2193
2200
  /**
2194
2201
  * Re-anchor an SDK-derived OAuth discovery URL from the MCP connection
2195
2202
  * (proxy) origin onto the actual MCP server.
@@ -2370,6 +2377,7 @@ var BrowserOAuthClientProvider = class {
2370
2377
  }
2371
2378
  saveTokens(tokens, ctx) {
2372
2379
  this.lastAttemptedAuthUrl = null;
2380
+ this.authorizationPending = false;
2373
2381
  return this.session.saveTokens(tokens, ctx);
2374
2382
  }
2375
2383
  async clientInformation(ctx) {
@@ -2450,6 +2458,7 @@ var BrowserOAuthClientProvider = class {
2450
2458
  }
2451
2459
  );
2452
2460
  this.lastAttemptedAuthUrl = prepared;
2461
+ this.authorizationPending = true;
2453
2462
  return prepared;
2454
2463
  }
2455
2464
  /**
@@ -2506,15 +2515,13 @@ var BrowserOAuthClientProvider = class {
2506
2515
  }
2507
2516
  clearStorage() {
2508
2517
  this.lastAttemptedAuthUrl = null;
2518
+ this.authorizationPending = false;
2509
2519
  const prefixPattern = `${this.storageKeyPrefix}_${this.serverUrlHash}_`;
2510
- const statePattern = `${this.storageKeyPrefix}:state_`;
2511
2520
  const keysToRemove = [];
2512
2521
  let count = 0;
2513
2522
  for (const key of this.storage.keys()) {
2514
2523
  if (key.startsWith(prefixPattern)) {
2515
2524
  keysToRemove.push(key);
2516
- } else if (key.startsWith(statePattern)) {
2517
- keysToRemove.push(key);
2518
2525
  }
2519
2526
  }
2520
2527
  const uniqueKeysToRemove = [...new Set(keysToRemove)];
@@ -2550,16 +2557,13 @@ var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
2550
2557
  return {
2551
2558
  // Core execution info
2552
2559
  execution_method: this.data.executionMethod,
2553
- query: this.data.query,
2554
2560
  query_length: this.data.query.length,
2555
2561
  success: this.data.success,
2556
2562
  // Agent configuration
2557
2563
  model_provider: this.data.modelProvider,
2558
2564
  model_name: this.data.modelName,
2559
2565
  server_count: this.data.serverCount,
2560
- server_identifiers: this.data.serverIdentifiers,
2561
2566
  total_tools_available: this.data.totalToolsAvailable,
2562
- tools_available_names: this.data.toolsAvailableNames,
2563
2567
  max_steps_configured: this.data.maxStepsConfigured,
2564
2568
  memory_enabled: this.data.memoryEnabled,
2565
2569
  use_server_manager: this.data.useServerManager,
@@ -2570,8 +2574,6 @@ var MCPAgentExecutionEvent = class extends BaseTelemetryEvent {
2570
2574
  // Execution results (always include, even if null)
2571
2575
  steps_taken: this.data.stepsTaken ?? null,
2572
2576
  tools_used_count: this.data.toolsUsedCount ?? null,
2573
- tools_used_names: this.data.toolsUsedNames ?? null,
2574
- response: this.data.response ?? null,
2575
2577
  response_length: this.data.response ? this.data.response.length : null,
2576
2578
  execution_time_ms: this.data.executionTimeMs ?? null,
2577
2579
  error_type: this.data.errorType ?? null,
@@ -2667,21 +2669,72 @@ async function telFetch(url, init) {
2667
2669
  }
2668
2670
  var POSTHOG_HOST = "https://eu.i.posthog.com";
2669
2671
  var POSTHOG_API_KEY = "phc_lyTtbYwvkdSbrcMQNPiKiiRWrrM1seyKIMjycSvItEI";
2670
- function capturePostHog(params) {
2671
- const host = params.host ?? POSTHOG_HOST;
2672
- const apiKey = params.apiKey ?? POSTHOG_API_KEY;
2673
- return telFetch(`${host}/i/v0/e/`, {
2674
- method: "POST",
2675
- headers: { "Content-Type": "application/json" },
2676
- keepalive: true,
2677
- body: JSON.stringify({
2672
+ var CONTENT_PROPERTY = /(^|_)(arguments?|args|body|command|headers?|location|message|query|response|secret|subject|token|uri|url|user_agent)(_|$)/i;
2673
+ var IDENTIFYING_PROPERTY = /(^|_)(server_identifiers?|server_names?|servers|tool_names?|tools_(available|used)_names)(_|$)/i;
2674
+ var AGGREGATE_PROPERTY = /(_count|_length|_duration(?:_ms)?|_time_ms|(^|_)num_[a-z0-9_]+)$/i;
2675
+ function normalizePropertyKey(key) {
2676
+ return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").replace(/[^a-z0-9_$]+/gi, "_").toLowerCase();
2677
+ }
2678
+ function sanitizeValue(value, seen) {
2679
+ if (Array.isArray(value)) {
2680
+ if (seen.has(value)) {
2681
+ throw new TypeError("Cyclic telemetry properties are not supported");
2682
+ }
2683
+ seen.add(value);
2684
+ const sanitized = value.map((item) => sanitizeValue(item, seen));
2685
+ seen.delete(value);
2686
+ return sanitized;
2687
+ }
2688
+ if (value !== null && typeof value === "object" && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)) {
2689
+ if (seen.has(value)) {
2690
+ throw new TypeError("Cyclic telemetry properties are not supported");
2691
+ }
2692
+ seen.add(value);
2693
+ const sanitized = sanitizeProperties(
2694
+ value,
2695
+ seen
2696
+ );
2697
+ seen.delete(value);
2698
+ return sanitized;
2699
+ }
2700
+ return value;
2701
+ }
2702
+ function sanitizeProperties(properties, seen = /* @__PURE__ */ new WeakSet()) {
2703
+ const sanitized = {};
2704
+ for (const [key, value] of Object.entries(properties)) {
2705
+ const normalizedKey = normalizePropertyKey(key);
2706
+ if (AGGREGATE_PROPERTY.test(normalizedKey)) {
2707
+ if (value === null || typeof value === "number") {
2708
+ sanitized[key] = value;
2709
+ }
2710
+ continue;
2711
+ }
2712
+ if (IDENTIFYING_PROPERTY.test(normalizedKey) || CONTENT_PROPERTY.test(normalizedKey)) {
2713
+ continue;
2714
+ }
2715
+ sanitized[key] = sanitizeValue(value, seen);
2716
+ }
2717
+ return sanitized;
2718
+ }
2719
+ async function capturePostHog(params) {
2720
+ try {
2721
+ const host = params.host ?? POSTHOG_HOST;
2722
+ const apiKey = params.apiKey ?? POSTHOG_API_KEY;
2723
+ const body = JSON.stringify({
2678
2724
  api_key: apiKey,
2679
2725
  event: params.event,
2680
2726
  distinct_id: params.distinctId,
2681
- properties: params.properties,
2727
+ properties: sanitizeProperties(params.properties),
2682
2728
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
2683
- })
2684
- });
2729
+ });
2730
+ await telFetch(`${host}/i/v0/e/`, {
2731
+ method: "POST",
2732
+ headers: { "Content-Type": "application/json" },
2733
+ keepalive: true,
2734
+ body
2735
+ });
2736
+ } catch {
2737
+ }
2685
2738
  }
2686
2739
 
2687
2740
  // src/telemetry/telemetry.ts
@@ -3013,11 +3066,12 @@ async function completeOAuthFlow(provider, serverUrl, options = {}) {
3013
3066
  throw new Error(`Unexpected OAuth auth() result: ${result}`);
3014
3067
  }
3015
3068
  }
3016
- if (typeof flowProvider.getAuthorizationCode === "function") {
3017
- const code = await flowProvider.getAuthorizationCode();
3069
+ if (typeof flowProvider.getAuthorizationResponse === "function" || typeof flowProvider.getAuthorizationCode === "function") {
3070
+ const response = typeof flowProvider.getAuthorizationResponse === "function" ? await flowProvider.getAuthorizationResponse() : { code: await flowProvider.getAuthorizationCode() };
3018
3071
  await auth(provider, {
3019
3072
  serverUrl,
3020
- authorizationCode: code,
3073
+ authorizationCode: response.code,
3074
+ ...response.iss !== void 0 ? { iss: response.iss } : {},
3021
3075
  fetchFn
3022
3076
  });
3023
3077
  return;
@@ -3031,6 +3085,8 @@ async function waitForBrowserAuthComplete(provider, timeoutMs) {
3031
3085
  );
3032
3086
  }
3033
3087
  if (provider.useRedirectFlow) {
3088
+ await new Promise(() => {
3089
+ });
3034
3090
  return;
3035
3091
  }
3036
3092
  const tokensKey = provider.getKey?.("tokens");
@@ -3047,25 +3103,29 @@ async function waitForBrowserAuthComplete(provider, timeoutMs) {
3047
3103
  } catch {
3048
3104
  }
3049
3105
  }
3050
- const result = await runAuthPopup({
3051
- popup: null,
3052
- state,
3053
- tokensKey,
3054
- timeoutMs
3055
- });
3056
- switch (result.kind) {
3057
- case "success":
3058
- return;
3059
- case "cancelled":
3060
- throw new Error("OAuth authentication was cancelled.");
3061
- case "timeout":
3062
- throw new Error(
3063
- `OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
3064
- );
3065
- case "error":
3066
- throw new Error(result.error);
3067
- default:
3068
- throw new Error("Unexpected OAuth popup result");
3106
+ try {
3107
+ const result = await runAuthPopup({
3108
+ popup: null,
3109
+ state,
3110
+ tokensKey,
3111
+ timeoutMs
3112
+ });
3113
+ switch (result.kind) {
3114
+ case "success":
3115
+ return;
3116
+ case "cancelled":
3117
+ throw new Error("OAuth authentication was cancelled.");
3118
+ case "timeout":
3119
+ throw new Error(
3120
+ `OAuth callback not received within ${timeoutMs}ms. Ensure /oauth/callback calls onMcpAuthorization().`
3121
+ );
3122
+ case "error":
3123
+ throw new Error(result.error);
3124
+ default:
3125
+ throw new Error("Unexpected OAuth popup result");
3126
+ }
3127
+ } finally {
3128
+ provider.markFlowComplete?.();
3069
3129
  }
3070
3130
  }
3071
3131
 
@@ -4544,6 +4604,16 @@ function useMcpOperations(params) {
4544
4604
  };
4545
4605
  }
4546
4606
 
4607
+ // src/react/token-expiry.ts
4608
+ function getOAuthTokenExpiry(tokens) {
4609
+ try {
4610
+ const payload = JSON.parse(atob(tokens.access_token?.split(".")[1] ?? ""));
4611
+ if (typeof payload.exp === "number") return payload.exp * 1e3;
4612
+ } catch {
4613
+ }
4614
+ return typeof tokens.expires_in === "number" ? Date.now() + tokens.expires_in * 1e3 : void 0;
4615
+ }
4616
+
4547
4617
  // src/react/useMcp.ts
4548
4618
  var DEFAULT_RECONNECT_DELAY = 3e3;
4549
4619
  var DEFAULT_RETRY_DELAY = 5e3;
@@ -5257,7 +5327,7 @@ function useMcp(options) {
5257
5327
  return "failed";
5258
5328
  }
5259
5329
  if (tokens?.access_token) {
5260
- const expiresAt = tokens.expires_in ? Date.now() + tokens.expires_in * 1e3 : void 0;
5330
+ const expiresAt = getOAuthTokenExpiry(tokens);
5261
5331
  let tokenEndpoint = null;
5262
5332
  let resource = null;
5263
5333
  let clientCreds = null;
@@ -5367,8 +5437,10 @@ function useMcp(options) {
5367
5437
  fetchFn: authProviderRef.current.getProxyFetch?.()
5368
5438
  });
5369
5439
  if (authResult === "REDIRECT") {
5370
- const authCode = await authProviderRef.current.getAuthorizationCode?.();
5371
- if (!authCode) {
5440
+ const flowProvider = authProviderRef.current;
5441
+ const authResponse = await flowProvider.getAuthorizationResponse?.();
5442
+ const authCode = authResponse?.code ?? await flowProvider.getAuthorizationCode?.();
5443
+ if (typeof authCode !== "string") {
5372
5444
  throw new Error(
5373
5445
  "Authorization code not captured by headless provider"
5374
5446
  );
@@ -5376,6 +5448,7 @@ function useMcp(options) {
5376
5448
  await auth2(authProviderRef.current, {
5377
5449
  serverUrl: url,
5378
5450
  authorizationCode: authCode,
5451
+ ...authResponse?.iss !== void 0 ? { iss: authResponse.iss } : {},
5379
5452
  fetchFn: authProviderRef.current.getProxyFetch?.()
5380
5453
  });
5381
5454
  }
@@ -5970,20 +6043,14 @@ function renderResult(title, message, error, returnUrl) {
5970
6043
  }
5971
6044
  document.body.appendChild(container);
5972
6045
  }
5973
- function findStoredState(state) {
5974
- const defaultKey = `mcp:auth:state_${state}`;
5975
- let key = localStorage.getItem(defaultKey) ? defaultKey : null;
5976
- if (!key) {
5977
- const suffix = `:state_${state}`;
5978
- for (let index = 0; index < localStorage.length; index++) {
5979
- const candidate = localStorage.key(index);
5980
- if (candidate?.endsWith(suffix)) {
5981
- key = candidate;
5982
- break;
5983
- }
5984
- }
5985
- }
5986
- const serialized = key ? localStorage.getItem(key) : null;
6046
+ async function findStoredState(state) {
6047
+ const store = new LocalStorageKVStore();
6048
+ const legacySuffix = `:state_${state}`;
6049
+ const scopedSuffix = `_state_${state}`;
6050
+ const key = (await store.keys()).find(
6051
+ (candidate) => candidate.endsWith(legacySuffix) || candidate.endsWith(scopedSuffix)
6052
+ );
6053
+ const serialized = key ? await store.get(key) : null;
5987
6054
  if (!key || !serialized) {
5988
6055
  throw new Error(`Invalid or expired OAuth state "${state}".`);
5989
6056
  }
@@ -5991,10 +6058,10 @@ function findStoredState(state) {
5991
6058
  try {
5992
6059
  value = JSON.parse(serialized);
5993
6060
  } catch {
5994
- localStorage.removeItem(key);
6061
+ await store.remove(key);
5995
6062
  throw new Error("Failed to parse stored OAuth state.");
5996
6063
  }
5997
- return { key, value };
6064
+ return { key, value, store };
5998
6065
  }
5999
6066
  function redirectWithError(returnUrl, message) {
6000
6067
  const url = new URL(returnUrl);
@@ -6053,17 +6120,19 @@ async function completeAuthorization() {
6053
6120
  const callbackParams = new URLSearchParams(window.location.search);
6054
6121
  const state = callbackParams.get("state");
6055
6122
  let stateKey = null;
6123
+ let stateStore = null;
6056
6124
  let storedState = null;
6057
6125
  let provider = null;
6058
6126
  try {
6059
6127
  if (!state) {
6060
6128
  throw new Error("OAuth callback is missing the state parameter.");
6061
6129
  }
6062
- const stored = findStoredState(state);
6130
+ const stored = await findStoredState(state);
6063
6131
  stateKey = stored.key;
6132
+ stateStore = stored.store;
6064
6133
  storedState = stored.value;
6065
6134
  if (!storedState.expiry || storedState.expiry < Date.now()) {
6066
- localStorage.removeItem(stateKey);
6135
+ await stateStore.remove(stateKey);
6067
6136
  throw new Error(
6068
6137
  "OAuth state has expired. Please start authentication again."
6069
6138
  );
@@ -6078,7 +6147,7 @@ async function completeAuthorization() {
6078
6147
  fetch: provider.getProxyFetch()
6079
6148
  });
6080
6149
  await transport.finishAuth(callbackParams);
6081
- localStorage.removeItem(stateKey);
6150
+ await stateStore.remove(stateKey);
6082
6151
  signalResult(true, void 0, storedState, {
6083
6152
  state,
6084
6153
  serverUrlHash: storedState.serverUrlHash
@@ -6086,8 +6155,12 @@ async function completeAuthorization() {
6086
6155
  } catch (error) {
6087
6156
  const message = error instanceof Error ? error.message : String(error);
6088
6157
  console.error("[mcp-callback] OAuth callback failed:", error);
6089
- if (stateKey) localStorage.removeItem(stateKey);
6090
- if (provider) localStorage.removeItem(provider.getKey("last_auth_url"));
6158
+ if (stateKey && stateStore) await stateStore.remove(stateKey);
6159
+ if (provider) {
6160
+ await (stateStore ?? new LocalStorageKVStore()).remove(
6161
+ provider.getKey("last_auth_url")
6162
+ );
6163
+ }
6091
6164
  signalResult(false, message, storedState, {
6092
6165
  state,
6093
6166
  serverUrlHash: storedState?.serverUrlHash
@@ -6963,7 +7036,13 @@ var LocalStorageProvider = class {
6963
7036
  );
6964
7037
  const serialized = JSON.stringify(sanitized);
6965
7038
  if (serialized !== stored) {
6966
- localStorage.setItem(this.storageKey, serialized);
7039
+ try {
7040
+ localStorage.setItem(this.storageKey, serialized);
7041
+ } catch {
7042
+ console.error(
7043
+ "[LocalStorageProvider] Failed to persist sanitized servers."
7044
+ );
7045
+ }
6967
7046
  }
6968
7047
  return sanitized;
6969
7048
  } catch {
@@ -7158,8 +7237,20 @@ function findOpeningConstructEnd(html, lowercasePrefix) {
7158
7237
  if (start === -1) return void 0;
7159
7238
  const boundary = lowercaseHtml[start + lowercasePrefix.length];
7160
7239
  if (boundary === ">" || boundary === " " || boundary === " " || boundary === "\n" || boundary === "\r" || boundary === "\f") {
7161
- const end = lowercaseHtml.indexOf(">", start + lowercasePrefix.length);
7162
- return end === -1 ? void 0 : end + 1;
7240
+ let quote;
7241
+ for (let index = start + lowercasePrefix.length; index < html.length; index++) {
7242
+ const character = html[index];
7243
+ if (quote) {
7244
+ if (character === quote) quote = void 0;
7245
+ continue;
7246
+ }
7247
+ if (character === '"' || character === "'") {
7248
+ quote = character;
7249
+ continue;
7250
+ }
7251
+ if (character === ">") return index + 1;
7252
+ }
7253
+ return void 0;
7163
7254
  }
7164
7255
  searchFrom = start + lowercasePrefix.length;
7165
7256
  }
@@ -7216,6 +7307,11 @@ function resolveViewResource(options) {
7216
7307
  }
7217
7308
 
7218
7309
  // src/react/view/sandbox-blob-url.ts
7310
+ function buildViewSandboxUrl(sandboxDocumentUrl, options) {
7311
+ const url = new URL(sandboxDocumentUrl.href);
7312
+ applySandboxSearchParams(url, options);
7313
+ return url;
7314
+ }
7219
7315
  function applySandboxSearchParams(url, options) {
7220
7316
  const { cspMode, permissions, widgetCsp } = options;
7221
7317
  url.searchParams.set(
@@ -7582,6 +7678,67 @@ var VIEW_DIMENSIONS = {
7582
7678
  FULLSCREEN_HEADER_HEIGHT: 50
7583
7679
  };
7584
7680
 
7681
+ // src/react/view/view-host-policy.ts
7682
+ function buildDefaultHostCapabilities({
7683
+ hasConnection,
7684
+ hasMessageHandler,
7685
+ hasModelContextHandler,
7686
+ hasLogHandler,
7687
+ hasSamplingHandler,
7688
+ hasDownloadHandler,
7689
+ messageCapabilities,
7690
+ modelContextCapabilities
7691
+ }) {
7692
+ return {
7693
+ openLinks: {},
7694
+ ...hasConnection ? {
7695
+ serverTools: {},
7696
+ serverResources: {}
7697
+ } : {},
7698
+ ...hasLogHandler ? { logging: {} } : {},
7699
+ ...hasSamplingHandler ? { sampling: {} } : {},
7700
+ ...hasDownloadHandler ? { downloadFile: {} } : {},
7701
+ ...hasModelContextHandler ? { updateModelContext: modelContextCapabilities ?? { text: {} } } : {},
7702
+ ...hasMessageHandler ? { message: messageCapabilities ?? { text: {} } } : {}
7703
+ };
7704
+ }
7705
+ function isToolVisibleToModel(tool) {
7706
+ if (!tool._meta || typeof tool._meta !== "object") return true;
7707
+ const ui = tool._meta.ui;
7708
+ if (!ui || typeof ui !== "object") return true;
7709
+ const visibility = ui.visibility;
7710
+ return !Array.isArray(visibility) || visibility.some((value) => value === "model");
7711
+ }
7712
+ async function dispatchUiMessage(handler, content) {
7713
+ if (!handler) {
7714
+ throw new Error("This host surface does not support ui/message");
7715
+ }
7716
+ if (content.length === 0) {
7717
+ throw new Error("ui/message requires at least one content block");
7718
+ }
7719
+ await handler(content);
7720
+ }
7721
+ function resolveRequestedDisplayMode({
7722
+ requested,
7723
+ current,
7724
+ hostAvailable,
7725
+ appAvailable
7726
+ }) {
7727
+ const hostModes = hostAvailable ?? ["inline"];
7728
+ const appModes = appAvailable ?? ["inline"];
7729
+ return hostModes.includes(requested) && appModes.includes(requested) ? requested : current;
7730
+ }
7731
+ function assertAppCanCallTool(tools, name) {
7732
+ const tool = tools?.find((candidate) => candidate.name === name);
7733
+ if (!tool) {
7734
+ throw new Error(`Tool "${name}" is not available to this app`);
7735
+ }
7736
+ const visibility = tool._meta?.ui?.visibility;
7737
+ if (visibility && !visibility.includes("app")) {
7738
+ throw new Error(`Tool "${name}" is not available to this app`);
7739
+ }
7740
+ }
7741
+
7585
7742
  // src/react/view/view-detection.ts
7586
7743
  function getViewResourceUri(toolMeta) {
7587
7744
  const uri = toolMeta?.ui;
@@ -7601,15 +7758,6 @@ function isViewResource(mimeType) {
7601
7758
  var DEFAULT_HOST_INFO = { name: "mcp-use-client", version: "2.0.0" };
7602
7759
  var DEFAULT_TOOL_CALL_TIMEOUT = 6e5;
7603
7760
  var SANDBOX_PROXY_READY = "ui/notifications/sandbox-proxy-ready";
7604
- var DEFAULT_HOST_CAPABILITIES = {
7605
- openLinks: {},
7606
- serverTools: {},
7607
- serverResources: {},
7608
- logging: {},
7609
- updateModelContext: { text: {} },
7610
- // ponytail: always advertised; bridge.onmessage no-ops when onMessage unset
7611
- message: { text: {} }
7612
- };
7613
7761
  function CloseIcon() {
7614
7762
  return /* @__PURE__ */ React2.createElement(
7615
7763
  "svg",
@@ -7673,12 +7821,17 @@ function ViewRendererBase({
7673
7821
  hostInfo = DEFAULT_HOST_INFO,
7674
7822
  hostContext,
7675
7823
  hostCapabilities,
7824
+ messageCapabilities,
7825
+ modelContextCapabilities,
7676
7826
  cspMode = "widget-declared",
7677
7827
  displayMode: displayModeProp,
7678
7828
  onDisplayModeChange,
7679
7829
  inlineMaxWidth = 768,
7680
7830
  chromeless,
7681
7831
  onMessage,
7832
+ onSamplingRequest,
7833
+ onDownloadFile,
7834
+ onAppToolsChanged,
7682
7835
  onModelContextUpdate,
7683
7836
  onLog,
7684
7837
  onReady,
@@ -7716,6 +7869,37 @@ function ViewRendererBase({
7716
7869
  );
7717
7870
  const [internalDisplayMode, setInternalDisplayMode] = useState4("inline");
7718
7871
  const displayMode = displayModeProp ?? internalDisplayMode;
7872
+ const hasMessageHandler = onMessage !== void 0;
7873
+ const hasModelContextHandler = onModelContextUpdate !== void 0;
7874
+ const hasLogHandler = onLog !== void 0;
7875
+ const hasSamplingHandler = onSamplingRequest !== void 0;
7876
+ const hasDownloadHandler = onDownloadFile !== void 0;
7877
+ const effectiveHostCapabilities = useMemo3(
7878
+ () => ({
7879
+ ...buildDefaultHostCapabilities({
7880
+ hasConnection: source.kind === "live",
7881
+ hasMessageHandler,
7882
+ hasModelContextHandler,
7883
+ hasLogHandler,
7884
+ hasSamplingHandler,
7885
+ hasDownloadHandler,
7886
+ messageCapabilities,
7887
+ modelContextCapabilities
7888
+ }),
7889
+ ...hostCapabilities
7890
+ }),
7891
+ [
7892
+ hostCapabilities,
7893
+ hasLogHandler,
7894
+ hasSamplingHandler,
7895
+ hasDownloadHandler,
7896
+ hasMessageHandler,
7897
+ hasModelContextHandler,
7898
+ messageCapabilities,
7899
+ modelContextCapabilities,
7900
+ source.kind
7901
+ ]
7902
+ );
7719
7903
  const effectiveHostContext = useMemo3(() => {
7720
7904
  if (!hostContext) return hostContext;
7721
7905
  if (hostContext.displayMode === displayMode) return hostContext;
@@ -7725,6 +7909,12 @@ function ViewRendererBase({
7725
7909
  hostContextRef.current = effectiveHostContext;
7726
7910
  const onMessageRef = useRef4(onMessage);
7727
7911
  onMessageRef.current = onMessage;
7912
+ const onSamplingRequestRef = useRef4(onSamplingRequest);
7913
+ onSamplingRequestRef.current = onSamplingRequest;
7914
+ const onDownloadFileRef = useRef4(onDownloadFile);
7915
+ onDownloadFileRef.current = onDownloadFile;
7916
+ const onAppToolsChangedRef = useRef4(onAppToolsChanged);
7917
+ onAppToolsChangedRef.current = onAppToolsChanged;
7728
7918
  const toolInputRef = useRef4(toolInput);
7729
7919
  toolInputRef.current = toolInput;
7730
7920
  const partialToolInputRef = useRef4(partialToolInput);
@@ -7941,7 +8131,7 @@ function ViewRendererBase({
7941
8131
  await readyPromise;
7942
8132
  if (disposed) return;
7943
8133
  const capabilities = {
7944
- ...hostCapabilities ?? DEFAULT_HOST_CAPABILITIES,
8134
+ ...effectiveHostCapabilities,
7945
8135
  sandbox: {
7946
8136
  csp: cspMode === "permissive" ? void 0 : resolved.csp,
7947
8137
  permissions: resolved.permissions
@@ -7950,75 +8140,110 @@ function ViewRendererBase({
7950
8140
  bridge = new AppBridge(null, hostInfo, capabilities, {
7951
8141
  hostContext: hostContextRef.current
7952
8142
  });
7953
- bridge.onmessage = async ({
7954
- content
7955
- }) => {
7956
- if (content.length > 0 && onMessageRef.current) {
7957
- onMessageRef.current(content);
7958
- }
7959
- return {};
7960
- };
8143
+ if (capabilities.message) {
8144
+ bridge.onmessage = async ({
8145
+ content
8146
+ }) => {
8147
+ await dispatchUiMessage(onMessageRef.current, content);
8148
+ return {};
8149
+ };
8150
+ }
8151
+ if (capabilities.sampling) {
8152
+ bridge.oncreatesamplingmessage = async (params) => {
8153
+ const handler = onSamplingRequestRef.current;
8154
+ if (!handler) {
8155
+ throw new Error("This host surface does not support sampling");
8156
+ }
8157
+ return handler(params);
8158
+ };
8159
+ }
8160
+ if (capabilities.downloadFile) {
8161
+ bridge.ondownloadfile = async (params) => {
8162
+ const handler = onDownloadFileRef.current;
8163
+ if (!handler) {
8164
+ throw new Error("This host surface does not support downloads");
8165
+ }
8166
+ return handler(params);
8167
+ };
8168
+ }
7961
8169
  bridge.onopenlink = async ({ url }) => {
7962
8170
  if (url) window.open(url, "_blank", "noopener,noreferrer");
7963
8171
  return {};
7964
8172
  };
7965
- bridge.oncalltool = (async ({
7966
- name,
7967
- arguments: args
7968
- }) => {
7969
- const conn = connectionRef.current;
7970
- if (!conn) throw new Error("Server connection not available");
7971
- try {
7972
- return await conn.callTool(name, args || {}, {
7973
- timeout: toolCallTimeout,
7974
- resetTimeoutOnProgress: true
7975
- });
7976
- } catch (error) {
7977
- bridge?.sendToolCancelled({
7978
- reason: error instanceof Error ? error.message : String(error)
7979
- });
7980
- throw error;
7981
- }
7982
- });
7983
- bridge.onreadresource = (async ({
7984
- uri
7985
- }) => {
7986
- const conn = connectionRef.current;
7987
- if (!conn) throw new Error("Server connection not available");
7988
- return await conn.readResource(uri);
7989
- });
7990
- bridge.onlistresources = (async () => {
7991
- const conn = connectionRef.current;
7992
- if (!conn) throw new Error("Server connection not available");
7993
- return { resources: [...conn.resources ?? []] };
7994
- });
8173
+ if (capabilities.serverTools) {
8174
+ bridge.oncalltool = (async ({
8175
+ name,
8176
+ arguments: args
8177
+ }) => {
8178
+ const conn = connectionRef.current;
8179
+ if (!conn) throw new Error("Server connection not available");
8180
+ assertAppCanCallTool(conn.tools, name);
8181
+ try {
8182
+ return await conn.callTool(name, args || {}, {
8183
+ timeout: toolCallTimeout,
8184
+ resetTimeoutOnProgress: true
8185
+ });
8186
+ } catch (error) {
8187
+ bridge?.sendToolCancelled({
8188
+ reason: error instanceof Error ? error.message : String(error)
8189
+ });
8190
+ throw error;
8191
+ }
8192
+ });
8193
+ }
8194
+ if (capabilities.serverResources) {
8195
+ bridge.onreadresource = (async ({
8196
+ uri
8197
+ }) => {
8198
+ const conn = connectionRef.current;
8199
+ if (!conn) throw new Error("Server connection not available");
8200
+ return await conn.readResource(uri);
8201
+ });
8202
+ bridge.onlistresources = (async () => {
8203
+ const conn = connectionRef.current;
8204
+ if (!conn) throw new Error("Server connection not available");
8205
+ return { resources: [...conn.resources ?? []] };
8206
+ });
8207
+ }
7995
8208
  bridge.onrequestdisplaymode = async ({
7996
8209
  mode
7997
8210
  }) => {
7998
8211
  const requested = mode ?? "inline";
7999
- const available = hostContextRef.current?.availableDisplayModes ?? [
8000
- "inline",
8001
- "pip",
8002
- "fullscreen"
8003
- ];
8004
- const effective = available.includes(requested) ? requested : displayModeRef.current;
8212
+ const effective = resolveRequestedDisplayMode({
8213
+ requested,
8214
+ current: displayModeRef.current,
8215
+ hostAvailable: hostContextRef.current?.availableDisplayModes,
8216
+ appAvailable: bridge?.getAppCapabilities()?.availableDisplayModes
8217
+ });
8005
8218
  await handleDisplayModeChangeRef.current(effective);
8006
8219
  return { mode: effective };
8007
8220
  };
8008
- bridge.onupdatemodelcontext = async ({
8009
- content,
8010
- structuredContent
8011
- }) => {
8012
- onModelContextUpdateRef.current?.({ content, structuredContent });
8013
- return {};
8014
- };
8015
- bridge.onloggingmessage = async ({
8016
- level,
8017
- data
8018
- }) => {
8019
- onLogRef.current?.({ level, data });
8020
- return {};
8021
- };
8221
+ if (capabilities.updateModelContext) {
8222
+ bridge.onupdatemodelcontext = async ({
8223
+ content,
8224
+ structuredContent
8225
+ }) => {
8226
+ if (!onModelContextUpdateRef.current) {
8227
+ throw new Error(
8228
+ "This host surface does not support model context updates"
8229
+ );
8230
+ }
8231
+ await onModelContextUpdateRef.current({
8232
+ content,
8233
+ structuredContent
8234
+ });
8235
+ return {};
8236
+ };
8237
+ }
8238
+ if (capabilities.logging) {
8239
+ bridge.onloggingmessage = async ({
8240
+ level,
8241
+ data
8242
+ }) => {
8243
+ onLogRef.current?.({ level, data });
8244
+ return {};
8245
+ };
8246
+ }
8022
8247
  bridge.onsizechange = async ({
8023
8248
  height
8024
8249
  }) => {
@@ -8028,6 +8253,35 @@ function ViewRendererBase({
8028
8253
  onInlineHeightChangeRef.current?.(height);
8029
8254
  }
8030
8255
  };
8256
+ let publishedAppToolsSignature = null;
8257
+ const publishAppTools = async () => {
8258
+ const handler = onAppToolsChangedRef.current;
8259
+ if (!bridge || !handler) return;
8260
+ const appCapabilities = bridge.getAppCapabilities();
8261
+ if (!appCapabilities?.tools) {
8262
+ handler(null);
8263
+ return;
8264
+ }
8265
+ const result = await bridge.listTools({});
8266
+ if (disposed || !bridge) return;
8267
+ const signature = JSON.stringify(result.tools);
8268
+ if (signature === publishedAppToolsSignature) return;
8269
+ publishedAppToolsSignature = signature;
8270
+ const currentBridge = bridge;
8271
+ handler({
8272
+ tools: result.tools,
8273
+ callTool: (name, args) => currentBridge.callTool({
8274
+ name,
8275
+ arguments: args ?? {}
8276
+ })
8277
+ });
8278
+ };
8279
+ bridge.setNotificationHandler(
8280
+ "notifications/tools/list_changed",
8281
+ async () => {
8282
+ await publishAppTools();
8283
+ }
8284
+ );
8031
8285
  const initPromise = hookInitialized(bridge);
8032
8286
  let transport = new PostMessageTransport(
8033
8287
  iframe.contentWindow,
@@ -8048,9 +8302,11 @@ function ViewRendererBase({
8048
8302
  bridgeRef.current = bridge;
8049
8303
  setInitCount((c) => c + 1);
8050
8304
  onLifecycleChangeRef.current?.({ status: "initialized" });
8305
+ await publishAppTools();
8051
8306
  const currentPartialToolInput = partialToolInputRef.current;
8052
- if (currentPartialToolInput) {
8053
- bridge.sendToolInputPartial({
8307
+ const hasCompletedToolResult = toolOutputRef.current !== void 0 && toolOutputRef.current !== null;
8308
+ if (currentPartialToolInput && !hasCompletedToolResult) {
8309
+ await bridge.sendToolInputPartial({
8054
8310
  arguments: currentPartialToolInput
8055
8311
  });
8056
8312
  } else {
@@ -8058,14 +8314,14 @@ function ViewRendererBase({
8058
8314
  ...toolInputRef.current,
8059
8315
  ...parseCustomProps(customPropsRef.current)
8060
8316
  };
8061
- bridge.sendToolInput({ arguments: mergedArgs });
8317
+ await bridge.sendToolInput({ arguments: mergedArgs });
8062
8318
  }
8063
8319
  const toolResultPayload = buildToolResultPayload(
8064
8320
  toolOutputRef.current,
8065
8321
  customPropsRef.current
8066
8322
  );
8067
8323
  if (toolResultPayload) {
8068
- bridge.sendToolResult(toolResultPayload);
8324
+ await bridge.sendToolResult(toolResultPayload);
8069
8325
  }
8070
8326
  onLifecycleChangeRef.current?.({ status: "ready" });
8071
8327
  } catch (err) {
@@ -8082,6 +8338,7 @@ function ViewRendererBase({
8082
8338
  disposed = true;
8083
8339
  const toClose = bridge;
8084
8340
  bridgeRef.current = null;
8341
+ onAppToolsChangedRef.current?.(null);
8085
8342
  if (!toClose) return;
8086
8343
  onLifecycleChangeRef.current?.({ status: "tearing-down" });
8087
8344
  void (async () => {
@@ -8104,7 +8361,7 @@ function ViewRendererBase({
8104
8361
  resolved,
8105
8362
  activeSandboxUrl,
8106
8363
  hostInfo,
8107
- hostCapabilities,
8364
+ effectiveHostCapabilities,
8108
8365
  cspMode,
8109
8366
  viewId,
8110
8367
  wrapTransport,
@@ -8114,33 +8371,37 @@ function ViewRendererBase({
8114
8371
  useEffect5(() => {
8115
8372
  const bridge = bridgeRef.current;
8116
8373
  if (!bridge || initCount === 0 || !effectiveHostContext) return;
8117
- bridge.setHostContext(effectiveHostContext);
8374
+ void bridge.setHostContext(effectiveHostContext);
8118
8375
  }, [effectiveHostContext, initCount]);
8119
8376
  useEffect5(() => {
8120
8377
  const bridge = bridgeRef.current;
8121
- if (!bridge || initCount === 0 || !partialToolInput) return;
8122
- bridge.sendToolInputPartial({ arguments: partialToolInput });
8123
- }, [initCount, partialToolInput]);
8378
+ if (!bridge || initCount === 0 || !partialToolInput || toolOutput !== void 0 && toolOutput !== null) {
8379
+ return;
8380
+ }
8381
+ void bridge.sendToolInputPartial({ arguments: partialToolInput });
8382
+ }, [initCount, partialToolInput, toolOutput]);
8124
8383
  useEffect5(() => {
8125
8384
  const bridge = bridgeRef.current;
8126
- if (!bridge || initCount === 0 || partialToolInput) return;
8385
+ if (!bridge || initCount === 0 || partialToolInput && (toolOutput === void 0 || toolOutput === null)) {
8386
+ return;
8387
+ }
8127
8388
  const mergedArgs = {
8128
8389
  ...toolInput,
8129
8390
  ...parseCustomProps(customProps)
8130
8391
  };
8131
- bridge.sendToolInput({ arguments: mergedArgs });
8132
- }, [initCount, toolInput, partialToolInput, customProps]);
8392
+ void bridge.sendToolInput({ arguments: mergedArgs });
8393
+ }, [initCount, toolInput, partialToolInput, customProps, toolOutput]);
8133
8394
  useEffect5(() => {
8134
8395
  const bridge = bridgeRef.current;
8135
8396
  if (!bridge || initCount === 0) return;
8136
8397
  const toolResultPayload = buildToolResultPayload(toolOutput, customProps);
8137
8398
  if (!toolResultPayload) return;
8138
- bridge.sendToolResult(toolResultPayload);
8399
+ void bridge.sendToolResult(toolResultPayload);
8139
8400
  }, [initCount, toolOutput, customProps]);
8140
8401
  useEffect5(() => {
8141
8402
  const bridge = bridgeRef.current;
8142
8403
  if (!bridge || initCount === 0 || !cancelled) return;
8143
- bridge.sendToolCancelled({ reason: "Cancelled by user" });
8404
+ void bridge.sendToolCancelled({ reason: "Cancelled by user" });
8144
8405
  }, [cancelled, initCount]);
8145
8406
  const readyFiredRef = useRef4(false);
8146
8407
  useEffect5(() => {
@@ -8270,6 +8531,14 @@ function viewRendererAreEqual(prev, next) {
8270
8531
  if (prev.customProps !== next.customProps) return false;
8271
8532
  if (prev.hostContext !== next.hostContext) return false;
8272
8533
  if (prev.hostCapabilities !== next.hostCapabilities) return false;
8534
+ if (prev.messageCapabilities !== next.messageCapabilities) return false;
8535
+ if (prev.modelContextCapabilities !== next.modelContextCapabilities)
8536
+ return false;
8537
+ if (prev.onMessage !== next.onMessage) return false;
8538
+ if (prev.onSamplingRequest !== next.onSamplingRequest) return false;
8539
+ if (prev.onDownloadFile !== next.onDownloadFile) return false;
8540
+ if (prev.onAppToolsChanged !== next.onAppToolsChanged) return false;
8541
+ if (prev.onModelContextUpdate !== next.onModelContextUpdate) return false;
8273
8542
  if (prev.cspMode !== next.cspMode) return false;
8274
8543
  if (prev.mockOpenAiFileApis !== next.mockOpenAiFileApis) return false;
8275
8544
  if (prev.onInlineHeightChange !== next.onInlineHeightChange) return false;
@@ -8288,12 +8557,15 @@ export {
8288
8557
  Tel,
8289
8558
  Telemetry,
8290
8559
  ViewRenderer,
8560
+ buildSandboxProxyBlobHtml,
8291
8561
  buildViewSandboxBlobUrl,
8562
+ buildViewSandboxUrl,
8292
8563
  clearRpcLogs,
8293
8564
  detectFavicon,
8294
8565
  getAllRpcLogs,
8295
8566
  getRpcLogs,
8296
8567
  getViewResourceUri,
8568
+ isToolVisibleToModel,
8297
8569
  isViewResource,
8298
8570
  isViewTool,
8299
8571
  onMcpAuthorization,