@cloudinary/asset-management-mcp 0.9.1 → 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.
Files changed (47) hide show
  1. package/README.md +12 -15
  2. package/bin/mcp-server.js +288 -369
  3. package/bin/mcp-server.js.map +13 -13
  4. package/esm/landing-page.d.ts.map +1 -1
  5. package/esm/landing-page.js +9 -3
  6. package/esm/landing-page.js.map +1 -1
  7. package/esm/lib/config.d.ts +3 -3
  8. package/esm/lib/config.js +3 -3
  9. package/esm/mcp-server/apps/app-shared.d.ts +6 -5
  10. package/esm/mcp-server/apps/app-shared.d.ts.map +1 -1
  11. package/esm/mcp-server/apps/app-shared.js +134 -14
  12. package/esm/mcp-server/apps/app-shared.js.map +1 -1
  13. package/esm/mcp-server/apps/asset-details-app.d.ts.map +1 -1
  14. package/esm/mcp-server/apps/asset-details-app.js +7 -14
  15. package/esm/mcp-server/apps/asset-details-app.js.map +1 -1
  16. package/esm/mcp-server/apps/asset-gallery-app.d.ts.map +1 -1
  17. package/esm/mcp-server/apps/asset-gallery-app.js +99 -306
  18. package/esm/mcp-server/apps/asset-gallery-app.js.map +1 -1
  19. package/esm/mcp-server/apps/asset-upload-app.d.ts.map +1 -1
  20. package/esm/mcp-server/apps/asset-upload-app.js +29 -24
  21. package/esm/mcp-server/apps/asset-upload-app.js.map +1 -1
  22. package/esm/mcp-server/apps/config.d.ts.map +1 -1
  23. package/esm/mcp-server/apps/config.js +1 -2
  24. package/esm/mcp-server/apps/config.js.map +1 -1
  25. package/esm/mcp-server/apps/extensions.d.ts.map +1 -1
  26. package/esm/mcp-server/apps/extensions.js +6 -1
  27. package/esm/mcp-server/apps/extensions.js.map +1 -1
  28. package/esm/mcp-server/cli/serve/impl.js +1 -1
  29. package/esm/mcp-server/cli/serve/impl.js.map +1 -1
  30. package/esm/mcp-server/mcp-server.js +1 -1
  31. package/esm/mcp-server/server.js +1 -1
  32. package/esm/types/bigint.d.ts.map +1 -1
  33. package/esm/types/bigint.js +4 -3
  34. package/esm/types/bigint.js.map +1 -1
  35. package/package.json +1 -1
  36. package/src/landing-page.ts +9 -3
  37. package/src/lib/config.ts +3 -3
  38. package/src/mcp-server/apps/app-shared.ts +135 -14
  39. package/src/mcp-server/apps/asset-details-app.ts +7 -13
  40. package/src/mcp-server/apps/asset-gallery-app.ts +99 -305
  41. package/src/mcp-server/apps/asset-upload-app.ts +29 -23
  42. package/src/mcp-server/apps/config.ts +1 -2
  43. package/src/mcp-server/apps/extensions.ts +6 -1
  44. package/src/mcp-server/cli/serve/impl.ts +1 -1
  45. package/src/mcp-server/mcp-server.ts +1 -1
  46. package/src/mcp-server/server.ts +1 -1
  47. package/src/types/bigint.ts +18 -17
@@ -116,6 +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 { scrollbar-gutter: auto; }
119
120
  body {
120
121
  font-family: var(--cld-font);
121
122
  background: var(--cld-bg);
@@ -124,18 +125,38 @@ body {
124
125
  line-height: 1.5;
125
126
  font-size: var(--cld-font-xs);
126
127
  position: relative;
128
+ overflow-x: hidden;
127
129
  }
128
130
  .theme-btn {
129
- position: absolute; top: 4px; right: 4px; z-index: 900;
130
131
  width: 22px; height: 22px; border-radius: 50%;
131
132
  border: 1px solid transparent; background: transparent;
132
133
  color: var(--cld-text3); cursor: pointer;
133
134
  display: flex; align-items: center; justify-content: center;
134
135
  padding: 0; transition: background 0.15s, color 0.15s, border-color 0.15s;
135
- opacity: 0.5;
136
+ opacity: 0.5; flex-shrink: 0;
136
137
  }
137
138
  .theme-btn:hover { background: var(--cld-bg3); color: var(--cld-text); border-color: var(--cld-border); opacity: 1; }
138
139
  .theme-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
140
+ /* Shared icon-button — square pill, same family as theme-btn */
141
+ .icon-btn {
142
+ display: inline-flex; align-items: center; justify-content: center; gap: 5px;
143
+ background: none; border: 1px solid var(--cld-border); border-radius: var(--cld-radius-sm);
144
+ color: var(--cld-text2); cursor: pointer; padding: 4px 8px;
145
+ font-size: 12px; font-weight: 500; font-family: inherit; line-height: 1;
146
+ transition: background 0.15s, color 0.15s, border-color 0.15s;
147
+ white-space: nowrap; flex-shrink: 0;
148
+ }
149
+ .icon-btn:hover { background: var(--cld-bg3); color: var(--cld-text); border-color: var(--cld-border2); }
150
+ .icon-btn svg { width: 13px; height: 13px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; flex-shrink: 0; }
151
+ /* icon-only variant (no text label) */
152
+ .icon-btn.icon-only { padding: 4px; width: 28px; height: 28px; }
153
+ /* header state icon (decorative, not a button) */
154
+ .upload-header-icon { display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
155
+ .upload-header-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
156
+ .upload-header-icon.icon-success { color: var(--cld-success); }
157
+ .upload-header-icon.icon-error { color: var(--cld-error); }
158
+ .upload-header-icon.icon-warning { color: var(--cld-warning); }
159
+ .upload-header-icon.icon-accent { color: var(--cld-accent); }
139
160
  `;
140
161
 
141
162
  // ── CSS: Shared component styles ────────────────────────────────────
@@ -143,9 +164,11 @@ export const SHARED_CSS_COMPONENTS = /* css */ `
143
164
  .link { cursor: pointer; }
144
165
  .link:hover { color: var(--cld-accent); text-decoration: underline; }
145
166
 
146
- /* 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). */
147
170
  .modal-overlay {
148
- position: fixed; inset: 0;
171
+ position: absolute; left: 0; right: 0;
149
172
  background: rgba(0,0,0,0.45);
150
173
  display: flex; align-items: center; justify-content: center;
151
174
  z-index: 1000; backdrop-filter: blur(3px); padding: 24px;
@@ -389,7 +412,8 @@ details.detail-section > summary.detail-section-title::-webkit-details-marker {
389
412
  }
390
413
  .upload-zone:hover { border-color: var(--cld-accent); background: var(--cld-accent-bg); }
391
414
  .upload-zone.dragover { border-color: var(--cld-accent); background: var(--cld-accent-bg); }
392
- .upload-zone-icon { font-size: 36px; margin-bottom: 8px; color: var(--cld-text3); }
415
+ .upload-zone-icon { margin-bottom: 8px; color: var(--cld-text3); display: flex; align-items: center; justify-content: center; }
416
+ .upload-zone-icon svg { width: 36px; height: 36px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
393
417
  .upload-zone-text { font-size: 14px; color: var(--cld-text2); margin-bottom: 4px; }
394
418
  .upload-zone-hint { font-size: 12px; color: var(--cld-text3); }
395
419
  .upload-zone-btn {
@@ -435,8 +459,9 @@ details.detail-section > summary.detail-section-title::-webkit-details-marker {
435
459
  .upload-preview-icon {
436
460
  width: 56px; height: 56px; border-radius: var(--cld-radius-sm);
437
461
  background: var(--cld-bg3); flex-shrink: 0; display: flex;
438
- align-items: center; justify-content: center; font-size: 24px; color: var(--cld-text3);
462
+ align-items: center; justify-content: center; color: var(--cld-text3);
439
463
  }
464
+ .upload-preview-icon svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
440
465
  .upload-preview-info { flex: 1; min-width: 0; }
441
466
  .upload-preview-name { font-size: 13px; font-weight: 600; color: var(--cld-text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
442
467
  .upload-preview-meta { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
@@ -585,19 +610,15 @@ details.upload-section > .upload-form { margin: 0; padding: 10px 12px; }
585
610
  background: var(--cld-accent-bg); border: 1px solid var(--cld-accent);
586
611
  border-radius: var(--cld-radius); margin-bottom: 4px; position: relative;
587
612
  }
588
- .upload-staged-icon { font-size: 24px; flex-shrink: 0; }
613
+ .upload-staged-icon { flex-shrink: 0; display: flex; align-items: center; color: var(--cld-accent); }
614
+ .upload-staged-icon svg { width: 24px; height: 24px; fill: none; stroke: currentColor; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }
589
615
  .upload-staged-info { flex: 1; min-width: 0; }
590
616
  .upload-staged-name {
591
617
  font-size: 13px; font-weight: 600; color: var(--cld-text);
592
618
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
593
619
  }
594
620
  .upload-staged-meta { font-size: 11px; color: var(--cld-text3); margin-top: 2px; }
595
- .upload-staged-clear {
596
- background: none; border: none; cursor: pointer; font-size: 16px;
597
- color: var(--cld-text3); padding: 4px 6px; border-radius: var(--cld-radius-sm);
598
- transition: color 0.15s, background 0.15s; flex-shrink: 0;
599
- }
600
- .upload-staged-clear:hover { color: var(--cld-error); background: rgba(206,25,13,0.08); }
621
+ .upload-staged-clear:hover { color: var(--cld-error); border-color: var(--cld-error); background: rgba(206,25,13,0.08); }
601
622
 
602
623
  /* Upload submit button */
603
624
  .upload-submit {
@@ -645,6 +666,24 @@ details.upload-section > .upload-form { margin: 0; padding: 10px 12px; }
645
666
  }
646
667
  `;
647
668
 
669
+ // ── JS: SVG icon strings (Lucide-style, 24px viewBox, stroke-based) ──
670
+ export const SHARED_JS_ICONS = /* js */ `
671
+ var IC = {
672
+ refresh: '<svg viewBox="0 0 24 24"><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/><path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/><path d="M3 21v-5h5"/></svg>',
673
+ chevronLeft:'<svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>',
674
+ arrowDown: '<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><polyline points="19 12 12 19 5 12"/></svg>',
675
+ x: '<svg viewBox="0 0 24 24"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>',
676
+ zap: '<svg viewBox="0 0 24 24"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>',
677
+ uploadCloud:'<svg viewBox="0 0 24 24"><polyline points="16 16 12 12 8 16"/><line x1="12" y1="12" x2="12" y2="21"/><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"/></svg>',
678
+ alertTriangle:'<svg viewBox="0 0 24 24"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>',
679
+ folderOpen: '<svg viewBox="0 0 24 24"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"/><polyline points="22 13 17 13 15 16 9 16 7 13 2 13"/></svg>',
680
+ checkCircle:'<svg viewBox="0 0 24 24"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>',
681
+ clock: '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>',
682
+ file: '<svg viewBox="0 0 24 24"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>',
683
+ image: '<svg viewBox="0 0 24 24"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>',
684
+ };
685
+ `;
686
+
648
687
  // ── JS: MCPApp client class ─────────────────────────────────────────
649
688
  export const SHARED_JS_MCP_CLIENT = /* js */ `
650
689
  var RPC_TIMEOUT_MS = 15000;
@@ -734,6 +773,29 @@ class MCPApp {
734
773
 
735
774
  // ── JS: Helper functions ────────────────────────────────────────────
736
775
  export const SHARED_JS_HELPERS = /* js */ `
776
+ function copyText(text) {
777
+ if (navigator.clipboard && navigator.clipboard.writeText) {
778
+ return navigator.clipboard.writeText(text).catch(function() { return _copyFallback(text); });
779
+ }
780
+ return _copyFallback(text);
781
+ }
782
+ function _copyFallback(text) {
783
+ return new Promise(function(resolve, reject) {
784
+ try {
785
+ var ta = document.createElement("textarea");
786
+ ta.value = text;
787
+ ta.setAttribute("readonly", "");
788
+ ta.style.position = "fixed"; ta.style.top = "0"; ta.style.left = "0";
789
+ ta.style.opacity = "0"; ta.style.pointerEvents = "none";
790
+ document.body.appendChild(ta);
791
+ ta.select(); ta.setSelectionRange(0, text.length);
792
+ var ok = document.execCommand("copy");
793
+ document.body.removeChild(ta);
794
+ if (ok) resolve(); else reject(new Error("Clipboard unavailable"));
795
+ } catch (e) { reject(e); }
796
+ });
797
+ }
798
+
737
799
  function fmtBytes(b) {
738
800
  if (!b) return "";
739
801
  var u = ["B","KB","MB","GB"], i = Math.min(Math.floor(Math.log(b)/Math.log(1024)), 3);
@@ -954,6 +1016,55 @@ function closeModal() {
954
1016
  if (ov) ov.remove();
955
1017
  }
956
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
+
957
1068
  function openModal(headerHtml, bodyHtml) {
958
1069
  closeModal();
959
1070
  var h = '<div class="modal-overlay"><div class="modal">';
@@ -963,6 +1074,7 @@ function openModal(headerHtml, bodyHtml) {
963
1074
  document.body.insertAdjacentHTML("beforeend", h);
964
1075
 
965
1076
  var overlay = document.querySelector(".modal-overlay");
1077
+ positionModalInVisibleArea(overlay);
966
1078
  overlay.addEventListener("click", function(e) {
967
1079
  if (e.target === overlay || e.target.classList.contains("modal-close")) {
968
1080
  closeModal();
@@ -1966,7 +2078,16 @@ function renderThemeToggle() {
1966
2078
  applyTheme();
1967
2079
  renderThemeToggle();
1968
2080
  });
1969
- document.body.appendChild(btn);
2081
+ var slot = document.getElementById("header-actions");
2082
+ if (slot) {
2083
+ slot.appendChild(btn);
2084
+ } else {
2085
+ btn.style.position = "absolute";
2086
+ btn.style.top = "4px";
2087
+ btn.style.right = "4px";
2088
+ btn.style.zIndex = "900";
2089
+ document.body.appendChild(btn);
2090
+ }
1970
2091
  }
1971
2092
 
1972
2093
  function setupHostContext(app) {
@@ -9,6 +9,7 @@
9
9
  import {
10
10
  SHARED_CSS_TOKENS,
11
11
  SHARED_CSS_COMPONENTS,
12
+ SHARED_JS_ICONS,
12
13
  SHARED_JS_MCP_CLIENT,
13
14
  SHARED_JS_HELPERS,
14
15
  SHARED_JS_TOOLTIPS,
@@ -59,16 +60,6 @@ const ASSET_DETAILS_CSS = /* css */ `
59
60
  padding: 2px 7px; border-radius: 4px; border: 1px solid var(--cld-border);
60
61
  }
61
62
 
62
- .open-link {
63
- padding: 6px 14px; border-radius: var(--cld-radius-sm);
64
- font-size: 12px; font-weight: 500; cursor: pointer;
65
- border: 1px solid var(--cld-accent); background: transparent;
66
- color: var(--cld-accent); font-family: inherit;
67
- transition: background 0.15s;
68
- white-space: nowrap; flex-shrink: 0;
69
- }
70
- .open-link:hover { background: var(--cld-accent-bg); }
71
-
72
63
  .hero-container {
73
64
  position: relative; margin-bottom: var(--cld-sp-md);
74
65
  border-radius: var(--cld-radius); overflow: hidden;
@@ -131,9 +122,9 @@ function renderPage(r) {
131
122
  if (dur) h += '<span class="pill">' + dur + "</span>";
132
123
  if (size) h += '<span class="pill">' + size + "</span>";
133
124
  h += "</div></div>";
134
- h += '<div style="display:flex;gap:6px;flex-shrink:0">';
135
- h += '<button class="open-link" id="refresh-asset" title="Refresh">\\u21BB</button>';
136
- if (url) h += '<button class="open-link" id="open-asset">Open</button>';
125
+ h += '<div id="header-actions" style="display:flex;gap:8px;flex-shrink:0;align-items:center">';
126
+ if (url) h += '<button class="icon-btn" id="open-asset">Open</button>';
127
+ h += '<button class="icon-btn icon-only" id="refresh-asset" title="Refresh">' + IC.refresh + '</button>';
137
128
  h += "</div>";
138
129
  h += "</div>";
139
130
 
@@ -175,6 +166,7 @@ function renderPage(r) {
175
166
  h += "</div>";
176
167
 
177
168
  root.innerHTML = h;
169
+ renderThemeToggle();
178
170
 
179
171
  // Event delegation
180
172
  root.addEventListener("click", function handler(e) {
@@ -220,6 +212,7 @@ function showFetchPrompt() {
220
212
  h += '<button class="prompt-btn prompt-btn-primary" id="fetch-direct-btn">Fetch Directly</button>';
221
213
  h += "</div></div>";
222
214
  root.innerHTML = h;
215
+ renderThemeToggle();
223
216
  document.getElementById("fetch-direct-btn").addEventListener("click", function() { fetchDirect(); });
224
217
  }
225
218
 
@@ -300,6 +293,7 @@ ${ASSET_DETAILS_CSS}
300
293
  <div id="app"><div class="status">Loading asset details&hellip;</div></div>
301
294
 
302
295
  <script>
296
+ ${SHARED_JS_ICONS}
303
297
  ${SHARED_JS_MCP_CLIENT}
304
298
  ${SHARED_JS_HELPERS}
305
299
  ${SHARED_JS_TOOLTIPS}