@cloudinary/asset-management-mcp 0.9.0 → 0.9.2
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/README.md +12 -15
- package/bin/mcp-server.js +269 -379
- package/bin/mcp-server.js.map +17 -17
- package/esm/funcs/searchSearchAssets.d.ts +46 -2
- package/esm/funcs/searchSearchAssets.d.ts.map +1 -1
- package/esm/funcs/searchSearchAssets.js +46 -2
- package/esm/funcs/searchSearchAssets.js.map +1 -1
- package/esm/landing-page.d.ts.map +1 -1
- package/esm/landing-page.js +9 -3
- package/esm/landing-page.js.map +1 -1
- package/esm/lib/config.d.ts +4 -4
- package/esm/lib/config.js +4 -4
- package/esm/mcp-server/apps/app-shared.d.ts +5 -4
- package/esm/mcp-server/apps/app-shared.d.ts.map +1 -1
- package/esm/mcp-server/apps/app-shared.js +79 -12
- package/esm/mcp-server/apps/app-shared.js.map +1 -1
- package/esm/mcp-server/apps/asset-details-app.d.ts.map +1 -1
- package/esm/mcp-server/apps/asset-details-app.js +7 -14
- package/esm/mcp-server/apps/asset-details-app.js.map +1 -1
- package/esm/mcp-server/apps/asset-gallery-app.d.ts.map +1 -1
- package/esm/mcp-server/apps/asset-gallery-app.js +75 -298
- package/esm/mcp-server/apps/asset-gallery-app.js.map +1 -1
- package/esm/mcp-server/apps/asset-upload-app.d.ts.map +1 -1
- package/esm/mcp-server/apps/asset-upload-app.js +29 -24
- package/esm/mcp-server/apps/asset-upload-app.js.map +1 -1
- package/esm/mcp-server/apps/config.d.ts.map +1 -1
- package/esm/mcp-server/apps/config.js +1 -2
- package/esm/mcp-server/apps/config.js.map +1 -1
- package/esm/mcp-server/apps/extensions.d.ts.map +1 -1
- package/esm/mcp-server/apps/extensions.js +6 -1
- package/esm/mcp-server/apps/extensions.js.map +1 -1
- package/esm/mcp-server/cli/serve/impl.js +1 -1
- package/esm/mcp-server/cli/serve/impl.js.map +1 -1
- package/esm/mcp-server/mcp-server.js +1 -1
- package/esm/mcp-server/server.js +1 -1
- package/esm/mcp-server/tools/searchSearchAssets.d.ts.map +1 -1
- package/esm/mcp-server/tools/searchSearchAssets.js +46 -2
- package/esm/mcp-server/tools/searchSearchAssets.js.map +1 -1
- package/esm/models/searchparameters.d.ts +4 -1
- package/esm/models/searchparameters.d.ts.map +1 -1
- package/esm/models/searchparameters.js +9 -8
- package/esm/models/searchparameters.js.map +1 -1
- package/esm/tool-names.js +1 -1
- package/esm/tool-names.js.map +1 -1
- package/esm/types/bigint.d.ts.map +1 -1
- package/esm/types/bigint.js +4 -3
- package/esm/types/bigint.js.map +1 -1
- package/package.json +1 -1
- package/src/funcs/searchSearchAssets.ts +46 -2
- package/src/landing-page.ts +9 -3
- package/src/lib/config.ts +4 -4
- package/src/mcp-server/apps/app-shared.ts +80 -12
- package/src/mcp-server/apps/asset-details-app.ts +7 -13
- package/src/mcp-server/apps/asset-gallery-app.ts +75 -297
- package/src/mcp-server/apps/asset-upload-app.ts +29 -23
- package/src/mcp-server/apps/config.ts +1 -2
- package/src/mcp-server/apps/extensions.ts +6 -1
- package/src/mcp-server/cli/serve/impl.ts +1 -1
- package/src/mcp-server/mcp-server.ts +1 -1
- package/src/mcp-server/server.ts +1 -1
- package/src/mcp-server/tools/searchSearchAssets.ts +46 -2
- package/src/models/searchparameters.ts +25 -9
- package/src/tool-names.ts +1 -1
- package/src/types/bigint.ts +18 -17
|
@@ -18,6 +18,7 @@ import { toJSONSchema } from "zod";
|
|
|
18
18
|
import {
|
|
19
19
|
SHARED_CSS_TOKENS,
|
|
20
20
|
SHARED_CSS_COMPONENTS,
|
|
21
|
+
SHARED_JS_ICONS,
|
|
21
22
|
SHARED_JS_MCP_CLIENT,
|
|
22
23
|
SHARED_JS_HELPERS,
|
|
23
24
|
SHARED_JS_TOOLTIPS,
|
|
@@ -61,15 +62,8 @@ const UPLOAD_CSS = /* css */ `
|
|
|
61
62
|
.upload-header h1 {
|
|
62
63
|
font-size: var(--cld-font-sm); font-weight: 600; color: var(--cld-text);
|
|
63
64
|
}
|
|
64
|
-
.upload-header-icon {
|
|
65
|
-
.upload-header {
|
|
66
|
-
.back-link {
|
|
67
|
-
position: absolute; top: -2px; right: 0;
|
|
68
|
-
background: none; border: none; cursor: pointer;
|
|
69
|
-
color: var(--cld-accent); font-size: var(--cld-font-xs);
|
|
70
|
-
padding: 2px 6px; border-radius: 4px;
|
|
71
|
-
}
|
|
72
|
-
.back-link:hover { text-decoration: underline; background: var(--cld-accent-bg); }
|
|
65
|
+
.upload-header-icon { display: flex; align-items: center; justify-content: center; }
|
|
66
|
+
.upload-header-icon svg { width: 20px; height: 20px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
|
|
73
67
|
|
|
74
68
|
.upload-result .detail-section { padding: 14px 16px; }
|
|
75
69
|
.upload-result .detail-section:first-child { padding-top: 0; }
|
|
@@ -508,16 +502,18 @@ function renderPicker() {
|
|
|
508
502
|
var h = "";
|
|
509
503
|
|
|
510
504
|
h += '<div class="upload-header">';
|
|
505
|
+
h += '<span class="upload-header-icon icon-accent">' + IC.uploadCloud + '</span>';
|
|
506
|
+
h += "<h1>Upload to Cloudinary</h1>";
|
|
507
|
+
h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto">';
|
|
511
508
|
if (lastResult) {
|
|
512
|
-
h += '<button class="
|
|
509
|
+
h += '<button class="icon-btn" id="back-to-result-btn">' + IC.chevronLeft + ' Back</button>';
|
|
513
510
|
}
|
|
514
|
-
h += '
|
|
515
|
-
h += "<h1>Upload to Cloudinary</h1>";
|
|
511
|
+
h += '</div>';
|
|
516
512
|
h += "</div>";
|
|
517
513
|
|
|
518
514
|
if (stagedFile) {
|
|
519
515
|
h += '<div class="upload-staged">';
|
|
520
|
-
h += '<div class="upload-staged-icon"
|
|
516
|
+
h += '<div class="upload-staged-icon">' + IC.file + '</div>';
|
|
521
517
|
h += '<div class="upload-staged-info">';
|
|
522
518
|
h += '<div class="upload-staged-name">' + esc(stagedFile.name) + "</div>";
|
|
523
519
|
if (stagedFile.size) {
|
|
@@ -528,11 +524,11 @@ function renderPicker() {
|
|
|
528
524
|
h += '<div class="upload-staged-meta">Remote URL</div>';
|
|
529
525
|
}
|
|
530
526
|
h += "</div>";
|
|
531
|
-
h += '<button class="upload-staged-clear" id="clear-staged-btn" title="Remove"
|
|
527
|
+
h += '<button class="upload-staged-clear icon-btn icon-only" id="clear-staged-btn" title="Remove">' + IC.x + '</button>';
|
|
532
528
|
h += "</div>";
|
|
533
529
|
} else {
|
|
534
530
|
h += '<div class="upload-zone" id="drop-zone">';
|
|
535
|
-
h += '<div class="upload-zone-icon"
|
|
531
|
+
h += '<div class="upload-zone-icon">' + IC.folderOpen + '</div>';
|
|
536
532
|
h += '<div class="upload-zone-text">Drag & drop a file here</div>';
|
|
537
533
|
h += '<div class="upload-zone-hint">Images, videos, PDFs, and other files up to 60 MB</div>';
|
|
538
534
|
h += '<button class="upload-zone-btn" id="browse-btn">Browse Files</button>';
|
|
@@ -556,6 +552,7 @@ function renderPicker() {
|
|
|
556
552
|
}
|
|
557
553
|
|
|
558
554
|
root.innerHTML = h;
|
|
555
|
+
renderThemeToggle();
|
|
559
556
|
|
|
560
557
|
var backBtn = document.getElementById("back-to-result-btn");
|
|
561
558
|
if (backBtn && lastResult) {
|
|
@@ -635,12 +632,13 @@ function renderUploading(name, meta) {
|
|
|
635
632
|
var h = "";
|
|
636
633
|
|
|
637
634
|
h += '<div class="upload-header">';
|
|
638
|
-
h += '<span class="upload-header-icon"
|
|
639
|
-
h +=
|
|
635
|
+
h += '<span class="upload-header-icon icon-accent">' + IC.uploadCloud + '</span>';
|
|
636
|
+
h += '<h1>Uploading…</h1>';
|
|
637
|
+
h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
|
|
640
638
|
h += "</div>";
|
|
641
639
|
|
|
642
640
|
h += '<div class="upload-preview">';
|
|
643
|
-
h += '<div class="upload-preview-icon"
|
|
641
|
+
h += '<div class="upload-preview-icon">' + IC.file + '</div>';
|
|
644
642
|
h += '<div class="upload-preview-info">';
|
|
645
643
|
h += '<div class="upload-preview-name">' + esc(name) + "</div>";
|
|
646
644
|
h += '<div class="upload-preview-meta">' + esc(meta) + "</div>";
|
|
@@ -652,6 +650,7 @@ function renderUploading(name, meta) {
|
|
|
652
650
|
h += "</div>";
|
|
653
651
|
|
|
654
652
|
root.innerHTML = h;
|
|
653
|
+
renderThemeToggle();
|
|
655
654
|
animateProgress();
|
|
656
655
|
}
|
|
657
656
|
|
|
@@ -705,7 +704,7 @@ function renderUploadError(title, msg) {
|
|
|
705
704
|
var header = document.querySelector(".upload-header h1");
|
|
706
705
|
if (header) header.textContent = title;
|
|
707
706
|
var icon = document.querySelector(".upload-header-icon");
|
|
708
|
-
if (icon) icon.
|
|
707
|
+
if (icon) { icon.innerHTML = IC.alertTriangle; icon.className = "upload-header-icon icon-warning"; }
|
|
709
708
|
|
|
710
709
|
var safeMsg = esc(msg).replace(/\\n/g, "<br>");
|
|
711
710
|
var h = '<div class="upload-error-msg">' + safeMsg + "</div>";
|
|
@@ -717,14 +716,16 @@ function renderUploadError(title, msg) {
|
|
|
717
716
|
var root = document.getElementById("app");
|
|
718
717
|
var safeMsg = esc(msg).replace(/\\n/g, "<br>");
|
|
719
718
|
var h = '<div class="upload-header">';
|
|
720
|
-
h += '<span class="upload-header-icon"
|
|
719
|
+
h += '<span class="upload-header-icon icon-warning">' + IC.alertTriangle + '</span>';
|
|
721
720
|
h += "<h1>" + esc(title) + "</h1>";
|
|
721
|
+
h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
|
|
722
722
|
h += "</div>";
|
|
723
723
|
h += '<div class="upload-error-msg">' + safeMsg + "</div>";
|
|
724
724
|
h += '<div class="upload-another" style="margin-top:14px;text-align:center">';
|
|
725
725
|
h += '<button class="prompt-btn prompt-btn-primary" id="retry-upload-btn">Try from App</button>';
|
|
726
726
|
h += "</div>";
|
|
727
727
|
root.innerHTML = h;
|
|
728
|
+
renderThemeToggle();
|
|
728
729
|
}
|
|
729
730
|
|
|
730
731
|
var btn = document.getElementById("retry-upload-btn");
|
|
@@ -769,8 +770,9 @@ function renderLocalFileNeeded(expectedName, errMsg) {
|
|
|
769
770
|
var classified = classifyFileError(errMsg);
|
|
770
771
|
var root = document.getElementById("app");
|
|
771
772
|
var h = '<div class="upload-header">';
|
|
772
|
-
h += '<span class="upload-header-icon"
|
|
773
|
+
h += '<span class="upload-header-icon icon-accent">' + IC.folderOpen + '</span>';
|
|
773
774
|
h += "<h1>" + esc(classified.title) + "</h1>";
|
|
775
|
+
h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
|
|
774
776
|
h += "</div>";
|
|
775
777
|
h += '<div class="prompt" style="margin-bottom:16px">';
|
|
776
778
|
h += '<div class="prompt-desc">The file <strong>' + esc(expectedName)
|
|
@@ -785,13 +787,14 @@ function renderLocalFileNeeded(expectedName, errMsg) {
|
|
|
785
787
|
}
|
|
786
788
|
h += "</div>";
|
|
787
789
|
h += '<div class="upload-zone" id="drop-zone">';
|
|
788
|
-
h += '<div class="upload-zone-icon"
|
|
790
|
+
h += '<div class="upload-zone-icon">' + IC.folderOpen + '</div>';
|
|
789
791
|
h += '<div class="upload-zone-text">Drop <strong>' + esc(expectedName) + "</strong> here</div>";
|
|
790
792
|
h += '<div class="upload-zone-hint">Or click to browse your files</div>';
|
|
791
793
|
h += '<button class="upload-zone-btn" id="browse-btn">Browse Files</button>';
|
|
792
794
|
h += '<input type="file" id="file-input" style="display:none">';
|
|
793
795
|
h += "</div>";
|
|
794
796
|
root.innerHTML = h;
|
|
797
|
+
renderThemeToggle();
|
|
795
798
|
|
|
796
799
|
function onFileSelected(file) {
|
|
797
800
|
var reader = new FileReader();
|
|
@@ -904,8 +907,9 @@ function renderResult(r) {
|
|
|
904
907
|
var h = "";
|
|
905
908
|
|
|
906
909
|
h += '<div class="upload-header">';
|
|
907
|
-
h += '<span class="upload-header-icon
|
|
910
|
+
h += '<span class="upload-header-icon ' + (isPending ? "icon-accent" : "icon-success") + '">' + (isPending ? IC.clock : IC.checkCircle) + '</span>';
|
|
908
911
|
h += "<h1>" + (isPending ? "Upload Queued" : "Upload Complete") + "</h1>";
|
|
912
|
+
h += '<div id="header-actions" style="display:flex;align-items:center;gap:6px;margin-left:auto"></div>';
|
|
909
913
|
h += "</div>";
|
|
910
914
|
|
|
911
915
|
h += '<div class="upload-result">';
|
|
@@ -966,6 +970,7 @@ function renderResult(r) {
|
|
|
966
970
|
h += "</div></div>";
|
|
967
971
|
|
|
968
972
|
root.innerHTML = h;
|
|
973
|
+
renderThemeToggle();
|
|
969
974
|
|
|
970
975
|
root.addEventListener("click", function handler(e) {
|
|
971
976
|
var el = e.target;
|
|
@@ -1102,6 +1107,7 @@ ${UPLOAD_CSS}
|
|
|
1102
1107
|
<div id="app"><div class="status">Preparing upload…</div></div>
|
|
1103
1108
|
|
|
1104
1109
|
<script>
|
|
1110
|
+
${SHARED_JS_ICONS}
|
|
1105
1111
|
${SHARED_JS_MCP_CLIENT}
|
|
1106
1112
|
${SHARED_JS_HELPERS}
|
|
1107
1113
|
${SHARED_JS_TOOLTIPS}
|
|
@@ -12,8 +12,7 @@ export const MCP_APPS: readonly McpApp[] = [
|
|
|
12
12
|
"asset-upload",
|
|
13
13
|
];
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
export const DEFAULT_MCP_APPS: readonly McpApp[] = [];
|
|
15
|
+
export const DEFAULT_MCP_APPS: readonly McpApp[] = [...MCP_APPS];
|
|
17
16
|
|
|
18
17
|
export function parseMcpAppsList(value: string): McpApp[] {
|
|
19
18
|
const parts = value.split(",").map((s) => s.trim()).filter(Boolean);
|
|
@@ -66,7 +66,12 @@ function appResourceContent(uri: URL, html: string) {
|
|
|
66
66
|
uri: uri.toString(),
|
|
67
67
|
mimeType: MCP_APP_MIME_TYPE,
|
|
68
68
|
text: html,
|
|
69
|
-
_meta: {
|
|
69
|
+
_meta: {
|
|
70
|
+
ui: {
|
|
71
|
+
csp: { resourceDomains: CSP_RESOURCE_DOMAINS },
|
|
72
|
+
permissions: { clipboardWrite: {} },
|
|
73
|
+
},
|
|
74
|
+
},
|
|
70
75
|
// deno-lint-ignore no-explicit-any
|
|
71
76
|
} as any],
|
|
72
77
|
};
|
|
@@ -42,7 +42,7 @@ async function startStreamableHTTP(cliFlags: ServeCommandFlags) {
|
|
|
42
42
|
app.use((req, res, next) => {
|
|
43
43
|
res.header("Access-Control-Allow-Origin", "*");
|
|
44
44
|
res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
|
45
|
-
res.header("Access-Control-Allow-Headers", "
|
|
45
|
+
res.header("Access-Control-Allow-Headers", "*");
|
|
46
46
|
if (req.method === "OPTIONS") {
|
|
47
47
|
res.sendStatus(204);
|
|
48
48
|
return;
|
package/src/mcp-server/server.ts
CHANGED
|
@@ -19,9 +19,53 @@ export const tool$searchSearchAssets: ToolDefinition<typeof args> = {
|
|
|
19
19
|
|
|
20
20
|
Returns a list of resources matching the specified search criteria.
|
|
21
21
|
|
|
22
|
-
Uses Lucene-like query language to
|
|
22
|
+
Uses a Lucene-like query language to filter assets by descriptive attributes (\`public_id\`, \`asset_id\`, \`filename\`, \`display_name\`, \`folder\` / \`asset_folder\`, \`tags\`, \`context.<key>\`), file details (\`resource_type\`, \`type\`, \`format\`, \`bytes\`, \`width\`, \`height\`, \`duration\`, \`pages\`, \`aspect_ratio\`, \`transparent\`, \`grayscale\`), lifecycle dates (\`uploaded_at\`, \`created_at\`, \`taken_at\`, \`updated_at\`, \`last_updated.<kind>\`), moderation and lifecycle state (\`status\`, \`moderation_status\`, \`moderation_kind\`), embedded data (\`image_metadata.*\`), structured metadata (\`metadata.<external_id>\`), and analysis fields (\`face_count\`, \`colors\`, \`quality_score\`, \`illustration_score\`, \`accessibility_analysis.*\`). Supports sorting, aggregate counts, and complex boolean expressions. See the \`expression\` parameter for the full field reference.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
## Expression syntax
|
|
25
|
+
|
|
26
|
+
- **Match**: \`field:value\` (token match) or \`field=value\` (exact match). Examples: \`tags:shirt\`, \`tags=cotton\`.
|
|
27
|
+
- **Comparisons**: \`>\`, \`<\`, \`>=\`, \`<=\` for numbers and dates. Example: \`bytes>10000000\`.
|
|
28
|
+
- **Ranges**: \`field:[from TO to]\` inclusive, \`field:{from TO to}\` exclusive. Example: \`width:{200 TO 1028}\`.
|
|
29
|
+
- **Booleans**: \`AND\`, \`OR\`, \`NOT\` (uppercase), or \`+\` (must), \`-\` (must not). \`NOT\` must appear between clauses — a bare leading \`NOT\` is a parse error; use \`-field:value\` to negate the first clause. Group with parentheses: \`(shirt OR pants) AND clothes\`.
|
|
30
|
+
- **Wildcards**: trailing \`*\` only, for prefix match (\`public_id:shoes_*\`, \`format:jp*\`, \`tags:shirt*\`). Not supported on \`folder\`, \`asset_folder\`, \`resource_type\`, or \`type\`. Leading \`*\`, middle \`*\`, \`?\`, and bare \`*\` (\`folder:*\`, \`context.alt:*\`) are all parse errors — wildcards cannot be used as a "field is present" probe.
|
|
31
|
+
- **Tokenized vs exact fields**: \`tags\`, \`filename\`, \`display_name\`, \`context.<key>\`, and \`metadata.<id>\` match on tokens split by whitespace and punctuation — \`tags:analysis\` matches the tag \`full-analysis\`. \`public_id\`, \`folder\`, \`asset_folder\`, and \`format\` match the whole value — \`public_id:dog\` will not match \`dog_pldcwy\`; use \`public_id="dog_pldcwy"\` (exact) or \`public_id:dog*\` (prefix). These exact-match fields still accept a trailing \`*\` for prefix match (except \`folder\` / \`asset_folder\`, where wildcards are ignored).
|
|
32
|
+
- **Dates**: ISO-8601 in quotes (\`uploaded_at>"2024-01-15"\`) or relative shorthand \`Nh\`, \`Nd\`, \`Nw\`, \`Nm\`, \`Ny\` (\`uploaded_at>1d\`, \`created_at:[4w TO 1w]\`). Send raw \`<\`/\`>\`, never HTML-escaped.
|
|
33
|
+
- **Quoting**: wrap any value containing a space, colon, or other reserved character (\`! ( ) { } [ ] ^ ~ ? \\ = & < > |\`) in double quotes, or escape each character with \`\\\`. Examples: \`tags:"service:mantels"\`, \`aspect_ratio:"16:9"\`, \`folder:"My Folder"\`.
|
|
34
|
+
|
|
35
|
+
## Common mistakes
|
|
36
|
+
|
|
37
|
+
- Use \`folder:\` or \`asset_folder:\` (singular); \`folders:\`, \`asset_folder_id:\`, and other invented variants are not valid fields. Pass the exact folder name — wildcards do not apply here.
|
|
38
|
+
- There is no "has any value" / presence probe. \`folder:*\`, \`metadata.alt:*\`, \`context.key:*\`, \`tags:*\`, and \`-tags:*\` are all parse errors. See *"Which assets have any value for \`metadata.<id>\`?"* under **Common tasks** for workarounds.
|
|
39
|
+
- \`NOT foo AND bar\` is a parse error. Write it as \`bar AND NOT foo\` or \`-foo AND bar\`, and keep every \`NOT\` between two clauses (\`a AND NOT b AND NOT c\` is fine; \`NOT b AND NOT c …\` is not).
|
|
40
|
+
- \`public_id:dog\` will not match \`dog_pldcwy\`. Use \`public_id="dog_pldcwy"\` (exact) or \`public_id:dog*\` (prefix).
|
|
41
|
+
- \`tags=service:mantels\` fails because the unquoted colon is parsed as a field separator. Use \`tags="service:mantels"\` or \`tags=service\\:mantels\`.
|
|
42
|
+
- Do not HTML-escape operators. Send \`uploaded_at<1h\`, not \`uploaded_at<1h\`.
|
|
43
|
+
- Do not leave an operand empty (e.g. \`tags: AND -tags:foo\`). Omit the empty clause entirely.
|
|
44
|
+
|
|
45
|
+
## Tips
|
|
46
|
+
|
|
47
|
+
- Set \`max_results: 0\` to return only \`total_count\` and \`aggregations\` without any resource payload — useful for counts and aggregation-only queries.
|
|
48
|
+
- \`total_count\` is always present in the response; prefer it over running an aggregation just to get a count.
|
|
49
|
+
- \`aggregate\` (both simple and range variants) and the \`metadata\`, \`image_metadata\`, \`image_analysis\` values of \`with_field\` require a Tier 2 search plan.
|
|
50
|
+
- Range aggregations require each range to include a \`key\` label (1–20 chars, \`[a-zA-Z0-9_-]+\`) and at least one of \`from\` / \`to\`.
|
|
51
|
+
|
|
52
|
+
## Common tasks
|
|
53
|
+
|
|
54
|
+
- **Count matching assets** — put the filter in \`expression\` with \`max_results: 0\` and read \`total_count\` from the response. Works on every tier; no \`aggregate\` needed.
|
|
55
|
+
- **Preview one matching asset** — set \`max_results: 1\`; add \`with_field: ["tags", "context"]\` (or \`metadata\`, Tier 2) to inspect values. Prefer this over fetching and scanning a full page.
|
|
56
|
+
- **Distribution of values for a field** — Tier 2: \`aggregate: [format|resource_type|type]\` for enum counts, or range aggregations on \`bytes\`, \`image_pixels\`, \`video_pixels\`, or \`duration\`. Tier 1 fallback: run N small queries with \`max_results: 0\`, one per candidate value, and read \`total_count\` from each.
|
|
57
|
+
- **"Which assets have any value for \`metadata.<id>\`?"** — not expressible directly (\`metadata.X:*\` is a parse error; there is no presence probe). Workarounds: (a) if the field has a known value set, enumerate — \`metadata.region:(apac OR emea OR amer)\`; (b) query broadly with \`with_field: ["metadata"]\` (Tier 2) and filter client-side for entries where the field is set; (c) at ingest time, attach a sentinel tag whenever the field is set, then search by that tag.
|
|
58
|
+
- **Newest / largest N** — keep the filter in \`expression\` and sort explicitly: \`sort_by: [{uploaded_at: "desc"}]\` with \`max_results: 10\`.
|
|
59
|
+
- **Filter by folder** — both \`asset_folder:"parent/child"\` and \`folder:"parent/child"\` match an exact folder path; there is no wildcard or "contains". To query across multiple folders, enumerate: \`asset_folder:("campaigns/2024" OR "campaigns/2025")\`.
|
|
60
|
+
- **Filter by metadata when you only know the label** — first call \`list-metadata-fields\` to resolve the label to an \`external_id\`, then query \`metadata.<external_id>:value\`.
|
|
61
|
+
- **Multiple independent filters in one turn** — prefer one \`expression\` with \`OR\` / parentheses over firing many parallel calls: \`metadata.region:apac OR metadata.region:emea\` in a single request is faster and more reliable than two parallel requests.
|
|
62
|
+
|
|
63
|
+
## Examples
|
|
64
|
+
|
|
65
|
+
- \`tags:shirt AND uploaded_at>1d\`
|
|
66
|
+
- \`resource_type:image AND bytes>1000000 AND (format:png OR format:jpg)\`
|
|
67
|
+
- \`folder:products AND context.category:electronics\`
|
|
68
|
+
- \`tags:"service:mantels" AND -tags:discontinued\`
|
|
25
69
|
`,
|
|
26
70
|
scopes: ["librarian"],
|
|
27
71
|
annotations: {
|
|
@@ -23,20 +23,30 @@ export const Type$zodSchema = z.enum([
|
|
|
23
23
|
]);
|
|
24
24
|
|
|
25
25
|
export type SearchParametersRange = {
|
|
26
|
+
key: string;
|
|
26
27
|
from?: number | undefined;
|
|
27
28
|
to?: number | undefined;
|
|
28
29
|
};
|
|
29
30
|
|
|
30
31
|
export const SearchParametersRange$zodSchema: z.ZodType<SearchParametersRange> =
|
|
31
32
|
z.object({
|
|
32
|
-
from: z.number().optional().describe(
|
|
33
|
-
|
|
33
|
+
from: z.number().optional().describe(
|
|
34
|
+
"Start of the range (inclusive). At least one of `from` / `to` is required.",
|
|
35
|
+
),
|
|
36
|
+
key: z.string().describe(
|
|
37
|
+
"A label for the bucket, returned in the aggregation response. 1–20 chars, alphanumeric plus `-` and `_`.",
|
|
38
|
+
),
|
|
39
|
+
to: z.number().optional().describe(
|
|
40
|
+
"End of the range (exclusive). At least one of `from` / `to` is required.",
|
|
41
|
+
),
|
|
34
42
|
});
|
|
35
43
|
|
|
36
44
|
export type Aggregate = { type: Type; ranges: Array<SearchParametersRange> };
|
|
37
45
|
|
|
38
46
|
export const Aggregate$zodSchema: z.ZodType<Aggregate> = z.object({
|
|
39
|
-
ranges: z.array(z.lazy(() => SearchParametersRange$zodSchema))
|
|
47
|
+
ranges: z.array(z.lazy(() => SearchParametersRange$zodSchema)).describe(
|
|
48
|
+
"One or more ranges for the numeric field. Each range must include a `key` label and at least one of `from` / `to`.\n",
|
|
49
|
+
),
|
|
40
50
|
type: Type$zodSchema,
|
|
41
51
|
});
|
|
42
52
|
|
|
@@ -54,14 +64,18 @@ export const AggregateEnum$zodSchema = z.enum([
|
|
|
54
64
|
]);
|
|
55
65
|
|
|
56
66
|
/**
|
|
57
|
-
* Fields or ranges to aggregate search results by.
|
|
67
|
+
* Fields or ranges to aggregate search results by. Requires a Tier 2 search plan; on Tier 1 the field is accepted but aggregations are omitted from the response.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
58
70
|
*/
|
|
59
71
|
export type AggregateUnion = Array<AggregateEnum> | Array<Aggregate>;
|
|
60
72
|
|
|
61
73
|
export const AggregateUnion$zodSchema: z.ZodType<AggregateUnion> = z.union([
|
|
62
74
|
z.array(AggregateEnum$zodSchema),
|
|
63
75
|
z.array(z.lazy(() => Aggregate$zodSchema)),
|
|
64
|
-
]).describe(
|
|
76
|
+
]).describe(
|
|
77
|
+
"Fields or ranges to aggregate search results by. Requires a Tier 2 search plan; on Tier 1 the field is accepted but aggregations are omitted from the response.\n",
|
|
78
|
+
);
|
|
65
79
|
|
|
66
80
|
export const WithField = {
|
|
67
81
|
Context: "context",
|
|
@@ -102,15 +116,17 @@ export const SearchParameters$zodSchema: z.ZodType<SearchParameters> = z.object(
|
|
|
102
116
|
aggregate: z.union([
|
|
103
117
|
z.array(AggregateEnum$zodSchema),
|
|
104
118
|
z.array(z.lazy(() => Aggregate$zodSchema)),
|
|
105
|
-
]).optional().describe(
|
|
119
|
+
]).optional().describe(
|
|
120
|
+
"Fields or ranges to aggregate search results by. Requires a Tier 2 search plan; on Tier 1 the field is accepted but aggregations are omitted from the response.\n",
|
|
121
|
+
),
|
|
106
122
|
expression: z.string().optional().describe(
|
|
107
|
-
"The search expression. Supports
|
|
123
|
+
"The Lucene-like search expression. Supports token match (`:`), exact match (`=`), trailing `*` for prefix match, ranges (`[a TO b]`, `{a TO b}`), and comparisons (`>`, `<`, `>=`, `<=`). Combine terms with uppercase `AND`, `OR`, `NOT`, or `+`/`-`. `NOT` must appear between clauses — a leading `NOT` is a parse error; use `-field:value` to negate the first clause. Group with parentheses.\n\nWrap values containing spaces, colons, or other reserved characters (`! ( ) { } [ ] ^ ~ ? \\ = & < > |`) in double quotes, e.g. `tags:\"service:mantels\"`, `aspect_ratio:\"16:9\"`. Send raw `<`/`>`, never HTML-escaped.\n\nWildcards are prefix-only (trailing `*`). A bare `*` (e.g. `folder:*`, `context.alt:*`, `metadata.key:*`, `tags:*`, `-tags:*`) is a parse error — there is no \"has any value\" / presence probe. Either drop the clause, use a concrete prefix, or filter on a known token.\n\nDates: ISO-8601 in quotes, or relative shorthand `1h`, `1d`, `1w`, `1m`, `1y` (`uploaded_at>1d`, `created_at:[4w TO 1w]`).\n\nSupported fields: `public_id`, `asset_id`, `filename`, `display_name`, `folder` / `asset_folder` (singular, not `folders`), `tags`, `context.<key>`, `metadata.<external_id>`, `resource_type`, `type`, `format`, `bytes`, `width`, `height`, `duration`, `pages`, `aspect_ratio`, `transparent`, `grayscale`, `status`, `moderation_status`, `moderation_kind`, `uploaded_at`, `created_at`, `taken_at`, `updated_at`, `last_updated.<kind>`, `face_count`, `illustration_score`, `quality_score`. Fields under `image_metadata.*`, `image_analysis.*`, `quality_analysis.*`, and `accessibility_analysis.*` also require the matching `with_field` to be returned in the response.\n\nSee the [search expressions guide](https://cloudinary.com/documentation/search_expressions.md) for the full reference.\n",
|
|
108
124
|
),
|
|
109
125
|
fields: z.string().optional().describe(
|
|
110
126
|
"A comma-separated list of fields to include in the response.\nNotes:\n- This parameter takes precedence over the with_field parameter, so if you want any additional asset attributes returned, make sure to also include them in this list (e.g., tags or context).\n- The following fields are always included in the response: public_id, asset_id, asset_folder, created_at, status, type, and resource_type.\n",
|
|
111
127
|
),
|
|
112
128
|
max_results: z.int().optional().describe(
|
|
113
|
-
"The maximum number of results to return. Default - 50. Maximum - 500
|
|
129
|
+
"The maximum number of results to return. Default - 50. Maximum - 500.\nSet to `0` to get only `total_count` and `aggregations` without any resources in the response — useful for counting or aggregation-only queries.\n",
|
|
114
130
|
),
|
|
115
131
|
next_cursor: z.string().optional().describe(
|
|
116
132
|
"The cursor value to get the next page of results. Available when a previous search returned more results than max_results.",
|
|
@@ -120,7 +136,7 @@ export const SearchParameters$zodSchema: z.ZodType<SearchParameters> = z.object(
|
|
|
120
136
|
"An array of single-key objects mapping a field to a sort direction. Each object must contain exactly one field name mapped to 'asc' or 'desc'.\nDefault: [{\"created_at\": \"desc\"}].\n",
|
|
121
137
|
),
|
|
122
138
|
with_field: z.array(WithField$zodSchema).optional().describe(
|
|
123
|
-
"The additional
|
|
139
|
+
"The additional asset attributes to include in each search result. The `fields` parameter takes precedence over this parameter. `image_metadata`, `image_analysis`, and `metadata` require a Tier 2 search plan.\n",
|
|
124
140
|
),
|
|
125
141
|
},
|
|
126
142
|
).describe("Common parameters for resource search operations.");
|
package/src/tool-names.ts
CHANGED
|
@@ -78,7 +78,7 @@ export const toolNames: Array<{ name: string; description: string }>= [
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"name": "search-assets",
|
|
81
|
-
"description": "Provides a powerful query interface to filter and retrieve assets and their details\n\nReturns a list of resources matching the specified search criteria.\n\nUses Lucene-like query language to
|
|
81
|
+
"description": "Provides a powerful query interface to filter and retrieve assets and their details\n\nReturns a list of resources matching the specified search criteria.\n\nUses a Lucene-like query language to filter assets by descriptive attributes (`public_id`, `asset_id`, `filename`, `display_name`, `folder` / `asset_folder`, `tags`, `context.<key>`), file details (`resource_type`, `type`, `format`, `bytes`, `width`, `height`, `duration`, `pages`, `aspect_ratio`, `transparent`, `grayscale`), lifecycle dates (`uploaded_at`, `created_at`, `taken_at`, `updated_at`, `last_updated.<kind>`), moderation and lifecycle state (`status`, `moderation_status`, `moderation_kind`), embedded data (`image_metadata.*`), structured metadata (`metadata.<external_id>`), and analysis fields (`face_count`, `colors`, `quality_score`, `illustration_score`, `accessibility_analysis.*`). Supports sorting, aggregate counts, and complex boolean expressions. See the `expression` parameter for the full field reference.\n\n## Expression syntax\n\n- **Match**: `field:value` (token match) or `field=value` (exact match). Examples: `tags:shirt`, `tags=cotton`.\n- **Comparisons**: `>`, `<`, `>=`, `<=` for numbers and dates. Example: `bytes>10000000`.\n- **Ranges**: `field:[from TO to]` inclusive, `field:{from TO to}` exclusive. Example: `width:{200 TO 1028}`.\n- **Booleans**: `AND`, `OR`, `NOT` (uppercase), or `+` (must), `-` (must not). `NOT` must appear between clauses — a bare leading `NOT` is a parse error; use `-field:value` to negate the first clause. Group with parentheses: `(shirt OR pants) AND clothes`.\n- **Wildcards**: trailing `*` only, for prefix match (`public_id:shoes_*`, `format:jp*`, `tags:shirt*`). Not supported on `folder`, `asset_folder`, `resource_type`, or `type`. Leading `*`, middle `*`, `?`, and bare `*` (`folder:*`, `context.alt:*`) are all parse errors — wildcards cannot be used as a \"field is present\" probe.\n- **Tokenized vs exact fields**: `tags`, `filename`, `display_name`, `context.<key>`, and `metadata.<id>` match on tokens split by whitespace and punctuation — `tags:analysis` matches the tag `full-analysis`. `public_id`, `folder`, `asset_folder`, and `format` match the whole value — `public_id:dog` will not match `dog_pldcwy`; use `public_id=\"dog_pldcwy\"` (exact) or `public_id:dog*` (prefix). These exact-match fields still accept a trailing `*` for prefix match (except `folder` / `asset_folder`, where wildcards are ignored).\n- **Dates**: ISO-8601 in quotes (`uploaded_at>\"2024-01-15\"`) or relative shorthand `Nh`, `Nd`, `Nw`, `Nm`, `Ny` (`uploaded_at>1d`, `created_at:[4w TO 1w]`). Send raw `<`/`>`, never HTML-escaped.\n- **Quoting**: wrap any value containing a space, colon, or other reserved character (`! ( ) { } [ ] ^ ~ ? \\ = & < > |`) in double quotes, or escape each character with `\\`. Examples: `tags:\"service:mantels\"`, `aspect_ratio:\"16:9\"`, `folder:\"My Folder\"`.\n\n## Common mistakes\n\n- Use `folder:` or `asset_folder:` (singular); `folders:`, `asset_folder_id:`, and other invented variants are not valid fields. Pass the exact folder name — wildcards do not apply here.\n- There is no \"has any value\" / presence probe. `folder:*`, `metadata.alt:*`, `context.key:*`, `tags:*`, and `-tags:*` are all parse errors. See *\"Which assets have any value for `metadata.<id>`?\"* under **Common tasks** for workarounds.\n- `NOT foo AND bar` is a parse error. Write it as `bar AND NOT foo` or `-foo AND bar`, and keep every `NOT` between two clauses (`a AND NOT b AND NOT c` is fine; `NOT b AND NOT c …` is not).\n- `public_id:dog` will not match `dog_pldcwy`. Use `public_id=\"dog_pldcwy\"` (exact) or `public_id:dog*` (prefix).\n- `tags=service:mantels` fails because the unquoted colon is parsed as a field separator. Use `tags=\"service:mantels\"` or `tags=service\\:mantels`.\n- Do not HTML-escape operators. Send `uploaded_at<1h`, not `uploaded_at<1h`.\n- Do not leave an operand empty (e.g. `tags: AND -tags:foo`). Omit the empty clause entirely.\n\n## Tips\n\n- Set `max_results: 0` to return only `total_count` and `aggregations` without any resource payload — useful for counts and aggregation-only queries.\n- `total_count` is always present in the response; prefer it over running an aggregation just to get a count.\n- `aggregate` (both simple and range variants) and the `metadata`, `image_metadata`, `image_analysis` values of `with_field` require a Tier 2 search plan.\n- Range aggregations require each range to include a `key` label (1–20 chars, `[a-zA-Z0-9_-]+`) and at least one of `from` / `to`.\n\n## Common tasks\n\n- **Count matching assets** — put the filter in `expression` with `max_results: 0` and read `total_count` from the response. Works on every tier; no `aggregate` needed.\n- **Preview one matching asset** — set `max_results: 1`; add `with_field: [\"tags\", \"context\"]` (or `metadata`, Tier 2) to inspect values. Prefer this over fetching and scanning a full page.\n- **Distribution of values for a field** — Tier 2: `aggregate: [format|resource_type|type]` for enum counts, or range aggregations on `bytes`, `image_pixels`, `video_pixels`, or `duration`. Tier 1 fallback: run N small queries with `max_results: 0`, one per candidate value, and read `total_count` from each.\n- **\"Which assets have any value for `metadata.<id>`?\"** — not expressible directly (`metadata.X:*` is a parse error; there is no presence probe). Workarounds: (a) if the field has a known value set, enumerate — `metadata.region:(apac OR emea OR amer)`; (b) query broadly with `with_field: [\"metadata\"]` (Tier 2) and filter client-side for entries where the field is set; (c) at ingest time, attach a sentinel tag whenever the field is set, then search by that tag.\n- **Newest / largest N** — keep the filter in `expression` and sort explicitly: `sort_by: [{uploaded_at: \"desc\"}]` with `max_results: 10`.\n- **Filter by folder** — both `asset_folder:\"parent/child\"` and `folder:\"parent/child\"` match an exact folder path; there is no wildcard or \"contains\". To query across multiple folders, enumerate: `asset_folder:(\"campaigns/2024\" OR \"campaigns/2025\")`.\n- **Filter by metadata when you only know the label** — first call `list-metadata-fields` to resolve the label to an `external_id`, then query `metadata.<external_id>:value`.\n- **Multiple independent filters in one turn** — prefer one `expression` with `OR` / parentheses over firing many parallel calls: `metadata.region:apac OR metadata.region:emea` in a single request is faster and more reliable than two parallel requests.\n\n## Examples\n\n- `tags:shirt AND uploaded_at>1d`\n- `resource_type:image AND bytes>1000000 AND (format:png OR format:jpg)`\n- `folder:products AND context.category:electronics`\n- `tags:\"service:mantels\" AND -tags:discontinued`\n"
|
|
82
82
|
},
|
|
83
83
|
{
|
|
84
84
|
"name": "visual-search-assets",
|
package/src/types/bigint.ts
CHANGED
|
@@ -25,23 +25,24 @@ export function bigint(): z.ZodType<bigint | string> {
|
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
export function bigintOptional(): z.ZodType<bigint | string | undefined> {
|
|
28
|
-
return z
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
28
|
+
return z
|
|
29
|
+
.union([
|
|
30
|
+
z.bigint().transform((v) => String(v)),
|
|
31
|
+
z.string().transform((v, ctx) => {
|
|
32
|
+
try {
|
|
33
|
+
return BigInt(v);
|
|
34
|
+
} catch {
|
|
35
|
+
ctx.addIssue({
|
|
36
|
+
code: z.ZodIssueCode.custom,
|
|
37
|
+
message: "Invalid bigint value",
|
|
38
|
+
});
|
|
39
|
+
return z.NEVER;
|
|
40
|
+
}
|
|
41
|
+
}),
|
|
42
|
+
z.number().transform((v) => BigInt(Math.trunc(v))),
|
|
43
|
+
z.null().transform(() => undefined),
|
|
44
|
+
])
|
|
45
|
+
.optional();
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
export function bigintNullable(): z.ZodType<bigint | string | null> {
|