@cloudinary/asset-management-mcp 0.9.2 → 0.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudinary/asset-management-mcp",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "mcpName": "io.github.cloudinary/asset-management-mcp",
5
5
  "author": "Cloudinary",
6
6
  "type": "module",
@@ -939,7 +939,7 @@ http_headers = { "api-key" = "YOUR_API_KEY", "api-secret" = "YOUR_API_SECRET", "
939
939
  <h1>Instructions</h1>
940
940
  <p>One-click installation for Claude Desktop users</p>
941
941
  <div class="instruction-item">
942
- <a href="https://github.com/cloudinary/asset-management-mcp/releases/download/v0.9.2/mcp-server.mcpb" download="mcp-server.mcpb" class="action-button header-action" style="display: inline-flex; margin-bottom: 16px;">
942
+ <a href="https://github.com/cloudinary/asset-management-mcp/releases/download/v0.9.3/mcp-server.mcpb" download="mcp-server.mcpb" class="action-button header-action" style="display: inline-flex; margin-bottom: 16px;">
943
943
  📥 Download MCP Bundle
944
944
  </a>
945
945
  </div>
package/src/lib/config.ts CHANGED
@@ -113,8 +113,8 @@ export function serverURLFromOptions(options: SDKOptions): URL | null {
113
113
  export const SDK_METADATA = {
114
114
  language: "typescript",
115
115
  openapiDocVersion: "0.5.1",
116
- sdkVersion: "0.9.2",
116
+ sdkVersion: "0.9.3",
117
117
  genVersion: "2.885.1",
118
118
  userAgent:
119
- "speakeasy-sdk/mcp-typescript 0.9.2 2.885.1 0.5.1 @cloudinary/asset-management-mcp",
119
+ "speakeasy-sdk/mcp-typescript 0.9.3 2.885.1 0.5.1 @cloudinary/asset-management-mcp",
120
120
  } as const;
@@ -116,7 +116,7 @@ export const SHARED_CSS_TOKENS = /* css */ `
116
116
  [data-theme="dark"] .status-warn, .dark .status-warn { background: #854d0e; color: #fef08a; }
117
117
  [data-theme="dark"] .status-err, .dark .status-err { background: #991b1b; color: #fecaca; }
118
118
 
119
- html { overflow: hidden; }
119
+ html { scrollbar-gutter: auto; }
120
120
  body {
121
121
  font-family: var(--cld-font);
122
122
  background: var(--cld-bg);
@@ -125,6 +125,7 @@ body {
125
125
  line-height: 1.5;
126
126
  font-size: var(--cld-font-xs);
127
127
  position: relative;
128
+ overflow-x: hidden;
128
129
  }
129
130
  .theme-btn {
130
131
  width: 22px; height: 22px; border-radius: 50%;
@@ -163,9 +164,11 @@ export const SHARED_CSS_COMPONENTS = /* css */ `
163
164
  .link { cursor: pointer; }
164
165
  .link:hover { color: var(--cld-accent); text-decoration: underline; }
165
166
 
166
- /* Modal */
167
+ /* Modal — positioned absolutely so it can be placed in the part of the
168
+ * iframe that is currently visible in the host's viewport (since position:fixed
169
+ * inside a tall iframe anchors to iframe-center, not the user's visible area). */
167
170
  .modal-overlay {
168
- position: fixed; inset: 0;
171
+ position: absolute; left: 0; right: 0;
169
172
  background: rgba(0,0,0,0.45);
170
173
  display: flex; align-items: center; justify-content: center;
171
174
  z-index: 1000; backdrop-filter: blur(3px); padding: 24px;
@@ -1013,6 +1016,55 @@ function closeModal() {
1013
1016
  if (ov) ov.remove();
1014
1017
  }
1015
1018
 
1019
+ /* Position the modal overlay so it's centered on the part of the iframe
1020
+ * the user is currently looking at. Inside an iframe that the host has
1021
+ * sized to scrollHeight, position: fixed anchors to iframe-center, not
1022
+ * the user's visible band — so we compute the visible band ourselves
1023
+ * via IntersectionObserver and place the overlay absolutely there. */
1024
+ function positionModalInVisibleArea(overlay) {
1025
+ if (!overlay) return;
1026
+ var docH = Math.max(document.documentElement.scrollHeight, window.innerHeight);
1027
+
1028
+ // Try IntersectionObserver first (works cross-origin too)
1029
+ if (typeof IntersectionObserver !== "undefined") {
1030
+ var probe = document.createElement("div");
1031
+ probe.style.cssText = "position:absolute;left:0;top:0;width:1px;height:" + docH + "px;pointer-events:none;visibility:hidden;";
1032
+ document.body.appendChild(probe);
1033
+ var io = new IntersectionObserver(function(entries) {
1034
+ try {
1035
+ var entry = entries[0];
1036
+ var rect = entry.intersectionRect;
1037
+ var rootRect = entry.rootBounds || entry.boundingClientRect;
1038
+ // intersectionRect.top is in viewport coords; visible band of the
1039
+ // document is from (probe.top + rect.top) to (probe.top + rect.bottom).
1040
+ var probeTop = entry.boundingClientRect.top; // viewport-relative
1041
+ var visibleTop = rect.top - probeTop; // document-relative
1042
+ var visibleHeight = rect.height;
1043
+ if (visibleHeight > 0) {
1044
+ overlay.style.top = visibleTop + "px";
1045
+ overlay.style.height = visibleHeight + "px";
1046
+ } else {
1047
+ // Fallback: probe not intersecting (shouldn't happen with full-height probe)
1048
+ overlay.style.top = (window.scrollY || 0) + "px";
1049
+ overlay.style.height = window.innerHeight + "px";
1050
+ }
1051
+ } finally {
1052
+ io.disconnect();
1053
+ if (probe.parentNode) probe.parentNode.removeChild(probe);
1054
+ }
1055
+ });
1056
+ io.observe(probe);
1057
+ return;
1058
+ }
1059
+
1060
+ // Fallback: same-origin only — read iframe's bounding rect
1061
+ var rect = document.documentElement.getBoundingClientRect();
1062
+ var visibleTop = rect.top < 0 ? -rect.top : 0;
1063
+ var visibleHeight = Math.min(window.innerHeight, rect.bottom) - Math.max(0, rect.top);
1064
+ overlay.style.top = visibleTop + "px";
1065
+ overlay.style.height = Math.max(visibleHeight, 200) + "px";
1066
+ }
1067
+
1016
1068
  function openModal(headerHtml, bodyHtml) {
1017
1069
  closeModal();
1018
1070
  var h = '<div class="modal-overlay"><div class="modal">';
@@ -1022,6 +1074,7 @@ function openModal(headerHtml, bodyHtml) {
1022
1074
  document.body.insertAdjacentHTML("beforeend", h);
1023
1075
 
1024
1076
  var overlay = document.querySelector(".modal-overlay");
1077
+ positionModalInVisibleArea(overlay);
1025
1078
  overlay.addEventListener("click", function(e) {
1026
1079
  if (e.target === overlay || e.target.classList.contains("modal-close")) {
1027
1080
  closeModal();
@@ -168,17 +168,27 @@ const GALLERY_CSS = /* css */ `
168
168
  }
169
169
 
170
170
  /* ── Multi-select bar ── */
171
+ /* Wrapper takes layout space ONLY when a selection is active. When idle
172
+ * it's display:none so it contributes 0 to scrollHeight (no phantom
173
+ * scrollbar gutter / no extra iframe height). */
174
+ .select-bar-wrap {
175
+ display: none;
176
+ position: sticky; bottom: 0; left: 0; right: 0;
177
+ justify-content: center; pointer-events: none;
178
+ z-index: 100; height: 64px;
179
+ }
180
+ .select-bar-wrap.visible { display: flex; }
171
181
  .select-bar {
172
- position: fixed; bottom: 16px; left: 50%;
173
- transform: translateX(-50%) translateY(80px);
182
+ position: absolute; bottom: 8px;
183
+ transform: translateY(80px);
174
184
  background: #1a1d24; color: white; border-radius: 40px;
175
185
  padding: 0 6px 0 16px; height: 48px;
176
186
  display: flex; align-items: center; gap: 4px;
177
187
  box-shadow: 0 8px 32px rgba(0,0,0,0.4);
178
188
  transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s;
179
- opacity: 0; pointer-events: none; z-index: 100; white-space: nowrap;
189
+ opacity: 0; pointer-events: none; white-space: nowrap;
180
190
  }
181
- .select-bar.visible { transform: translateX(-50%) translateY(0); opacity: 1; pointer-events: all; }
191
+ .select-bar-wrap.visible .select-bar { transform: translateY(0); opacity: 1; pointer-events: all; }
182
192
  .select-count { font-size: 13px; font-weight: 600; margin-right: 8px; }
183
193
  .bar-btn {
184
194
  height: 36px; padding: 0 14px; border: none; border-radius: 30px;
@@ -243,12 +253,12 @@ function showToast(msg) {
243
253
  }
244
254
 
245
255
  function updateSelectBar() {
246
- var bar = document.getElementById("select-bar");
256
+ var wrap = document.getElementById("select-bar-wrap");
247
257
  var countEl = document.getElementById("select-count");
248
- if (!bar || !countEl) return;
258
+ if (!wrap || !countEl) return;
249
259
  var n = selected.size;
250
260
  countEl.textContent = n + " selected";
251
- bar.classList.toggle("visible", n > 0);
261
+ wrap.classList.toggle("visible", n > 0);
252
262
  var btn = document.getElementById("select-all-btn");
253
263
  if (btn) {
254
264
  var visible = getVisibleIndices();
@@ -528,7 +538,8 @@ function render() {
528
538
  h += "</div>";
529
539
  }
530
540
 
531
- // Multi-select bar
541
+ // Multi-select bar (sticky, in-flow wrapper so iframe sizing stays truthful)
542
+ h += '<div class="select-bar-wrap" id="select-bar-wrap">';
532
543
  h += '<div class="select-bar" id="select-bar">';
533
544
  h += '<span class="select-count" id="select-count">0 selected</span>';
534
545
  h += '<div class="bar-divider"></div>';
@@ -538,6 +549,7 @@ function render() {
538
549
  h += '<div class="bar-divider"></div>';
539
550
  h += '<button class="bar-btn bar-ghost" id="bar-clear">' + IC.x + '</button>';
540
551
  h += '</div>';
552
+ h += '</div>';
541
553
 
542
554
  // Toast
543
555
  h += '<div class="gallery-toast" id="gallery-toast"></div>';
@@ -767,6 +779,9 @@ async function fetchDirect() {
767
779
  console.log(LOG_PREFIX, "fetchDirect ->", name);
768
780
 
769
781
  document.getElementById("app").innerHTML = '<div class="status">Fetching assets\\u2026</div>';
782
+ requestAnimationFrame(function() {
783
+ app.reportSize(Math.max(document.documentElement.scrollHeight, MIN_HEIGHT));
784
+ });
770
785
  try {
771
786
  var res = await app.callServerTool({ name: name, arguments: args });
772
787
  var data = ingestResult(res);
@@ -818,6 +833,7 @@ async function loadMore() {
818
833
  function refreshGallery() {
819
834
  allResources = [];
820
835
  lastCursor = null;
836
+ selected.clear();
821
837
  fetchDirect();
822
838
  }
823
839
 
@@ -22,7 +22,7 @@ const routes = buildRouteMap({
22
22
  export const app = buildApplication(routes, {
23
23
  name: "mcp",
24
24
  versionInfo: {
25
- currentVersion: "0.9.2",
25
+ currentVersion: "0.9.3",
26
26
  },
27
27
  });
28
28
 
@@ -60,7 +60,7 @@ export function createMCPServer(deps: {
60
60
  }) {
61
61
  const server = new McpServer({
62
62
  name: "CloudinaryAssetMgmt",
63
- version: "0.9.2",
63
+ version: "0.9.3",
64
64
  });
65
65
 
66
66
  const getClient = deps.getSDK || (() =>