@buildinternet/uploads 0.11.0 → 0.11.1
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 +2 -2
- package/dist/async.d.ts +5 -0
- package/dist/async.js +21 -0
- package/dist/cli-catalog.js +1 -1
- package/dist/commands/install.js +5 -0
- package/dist/commands.d.ts +91 -1
- package/dist/commands.js +350 -139
- package/dist/mcp/batch-error.d.ts +16 -0
- package/dist/mcp/batch-error.js +24 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +16 -0
- package/dist/mcp/tools.d.ts +1 -1
- package/dist/mcp/tools.js +170 -94
- package/package.json +1 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool handler failure that still carries structuredContent (e.g. multi-file
|
|
3
|
+
* total failure with a `failures` array). The MCP server maps this to
|
|
4
|
+
* isError: true while preserving structuredContent for agents.
|
|
5
|
+
*/
|
|
6
|
+
export declare class ToolBatchError extends Error {
|
|
7
|
+
readonly structuredContent: unknown;
|
|
8
|
+
constructor(message: string, structuredContent: unknown);
|
|
9
|
+
}
|
|
10
|
+
/** One-line summary of a multi-file failure list. */
|
|
11
|
+
export declare function batchFailureMessage(failures: readonly {
|
|
12
|
+
file: string;
|
|
13
|
+
error: {
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
}[]): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool handler failure that still carries structuredContent (e.g. multi-file
|
|
3
|
+
* total failure with a `failures` array). The MCP server maps this to
|
|
4
|
+
* isError: true while preserving structuredContent for agents.
|
|
5
|
+
*/
|
|
6
|
+
export class ToolBatchError extends Error {
|
|
7
|
+
structuredContent;
|
|
8
|
+
constructor(message, structuredContent) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "ToolBatchError";
|
|
11
|
+
this.structuredContent = structuredContent;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** One-line summary of a multi-file failure list. */
|
|
15
|
+
export function batchFailureMessage(failures) {
|
|
16
|
+
if (failures.length === 0)
|
|
17
|
+
return "upload failed";
|
|
18
|
+
if (failures.length === 1) {
|
|
19
|
+
const f = failures[0];
|
|
20
|
+
return `${f.file}: ${f.error.message}`;
|
|
21
|
+
}
|
|
22
|
+
const lines = failures.map((f) => ` ${f.file}: ${f.error.message}`);
|
|
23
|
+
return `${failures.length} uploads failed:\n${lines.join("\n")}`;
|
|
24
|
+
}
|
package/dist/mcp/server.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { METADATA_DESCRIPTION, metadataProp, optPosInt, optString, optStringArray, optStringRecord, usage, type ToolArgs, } from "./args.js";
|
|
2
|
+
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
2
3
|
export interface McpTool {
|
|
3
4
|
name: string;
|
|
4
5
|
description: string;
|
package/dist/mcp/server.js
CHANGED
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import { UploadsError } from "../errors.js";
|
|
11
11
|
import { errorCodeFromUnknown, recordEvent } from "../telemetry.js";
|
|
12
|
+
import { ToolBatchError } from "./batch-error.js";
|
|
12
13
|
export { METADATA_DESCRIPTION, metadataProp, optPosInt, optString, optStringArray, optStringRecord, usage, } from "./args.js";
|
|
14
|
+
export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
|
|
13
15
|
const SUPPORTED_PROTOCOL_VERSIONS = new Set(["2025-06-18", "2025-03-26", "2024-11-05"]);
|
|
14
16
|
const LATEST_PROTOCOL_VERSION = "2025-06-18";
|
|
15
17
|
function response(id, result) {
|
|
@@ -59,6 +61,20 @@ export function createMcpServer(opts) {
|
|
|
59
61
|
durationMs: Date.now() - start,
|
|
60
62
|
errorCode: errorCodeFromUnknown(err),
|
|
61
63
|
}, { apiUrl });
|
|
64
|
+
// Multi-file total failure: keep structuredContent so agents see every
|
|
65
|
+
// per-file error, not only the first message string.
|
|
66
|
+
if (err instanceof ToolBatchError) {
|
|
67
|
+
return response(id, {
|
|
68
|
+
content: [
|
|
69
|
+
{
|
|
70
|
+
type: "text",
|
|
71
|
+
text: JSON.stringify(err.structuredContent, null, 2),
|
|
72
|
+
},
|
|
73
|
+
],
|
|
74
|
+
structuredContent: err.structuredContent,
|
|
75
|
+
isError: true,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
62
78
|
return response(id, {
|
|
63
79
|
content: [{ type: "text", text: toolErrorText(err) }],
|
|
64
80
|
isError: true,
|
package/dist/mcp/tools.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import type { GlobalFlags } from "../cli-args.js";
|
|
|
2
2
|
import { type UploadsClient } from "../client.js";
|
|
3
3
|
import { type UploadsClientConfig } from "../config.js";
|
|
4
4
|
import { type CommandRunner } from "../github-gh.js";
|
|
5
|
-
import type
|
|
5
|
+
import { type McpTool } from "./server.js";
|
|
6
6
|
export declare function createUploadsMcpTools(opts: {
|
|
7
7
|
globals: GlobalFlags;
|
|
8
8
|
runner?: CommandRunner;
|
package/dist/mcp/tools.js
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MCP tool set mirroring the CLI commands (put, attach, list, delete,
|
|
3
|
-
* usage, reconcile, purge_expired, comment, health, doctor). Config is
|
|
4
|
-
* resolved fresh per tool call so a
|
|
5
|
-
* per-call `workspace` argument behaves like the CLI's --workspace flag, and
|
|
6
|
-
* a missing token surfaces as a tool error rather than a startup failure.
|
|
7
|
-
*/
|
|
8
|
-
import { basename } from "node:path";
|
|
9
1
|
import { createUploadsClient } from "../client.js";
|
|
10
|
-
import { buildDoctorReport, makeGhTarget, prepareImageForUpload,
|
|
2
|
+
import { buildDoctorReport, makeGhTarget, prepareImageForUpload, syncAttachmentsComment, uploadAttachments, uploadPuts, } from "../commands.js";
|
|
11
3
|
import { resolveFrameId } from "../frame.js";
|
|
12
4
|
import { resolveConfig, resolvePutDefaults, } from "../config.js";
|
|
13
5
|
import { buildMarkdown } from "../embed.js";
|
|
@@ -19,6 +11,7 @@ import { rewriteKeyExtension } from "../optimize.js";
|
|
|
19
11
|
import { buildCliProvenance } from "../provenance.js";
|
|
20
12
|
import { execRunner, resolveCurrentPullRequest, resolveRepo, } from "../github-gh.js";
|
|
21
13
|
import { METADATA_DESCRIPTION, metadataProp, optPosInt, optString, optStringArray, optStringRecord, usage, } from "./args.js";
|
|
14
|
+
import { batchFailureMessage, ToolBatchError } from "./server.js";
|
|
22
15
|
import { attachmentFromText, buildReportPayload, parseReportType, REPORT_TYPES, submitReport, validateReportMessage, } from "../report.js";
|
|
23
16
|
import { resolveApiUrl } from "../config.js";
|
|
24
17
|
function optBool(args, name) {
|
|
@@ -270,13 +263,18 @@ export function createUploadsMcpTools(opts) {
|
|
|
270
263
|
},
|
|
271
264
|
{
|
|
272
265
|
name: "put",
|
|
273
|
-
description: "Upload
|
|
266
|
+
description: "Upload one or more files to uploads.sh and get public URL(s) plus GitHub-ready embed markdown. Single-file: pass `file` or `contentBase64`+`filename` (flat result with `url`/`embedUrl`/`markdown`). Multi-file: pass `files` (paths; parallel; returns `uploads`+`failures`). Prefer `embedUrl` in PR/issue markdown. With `pr`/`issue` keys are stable and `comment` syncs the managed attachments comment. All uploads are public; pr/issue keys are predictable — upload only non-sensitive media.",
|
|
274
267
|
inputSchema: {
|
|
275
268
|
type: "object",
|
|
276
269
|
properties: {
|
|
277
270
|
file: {
|
|
278
271
|
type: "string",
|
|
279
|
-
description: "Path of
|
|
272
|
+
description: "Path of a single file to upload. Exactly one of file, files, or contentBase64 is required.",
|
|
273
|
+
},
|
|
274
|
+
files: {
|
|
275
|
+
type: "array",
|
|
276
|
+
items: { type: "string" },
|
|
277
|
+
description: "Paths of multiple files to upload in parallel. Returns { uploads, failures }. Cannot combine with file, contentBase64, key, or filename.",
|
|
280
278
|
},
|
|
281
279
|
contentBase64: {
|
|
282
280
|
type: "string",
|
|
@@ -284,11 +282,11 @@ export function createUploadsMcpTools(opts) {
|
|
|
284
282
|
},
|
|
285
283
|
filename: {
|
|
286
284
|
type: "string",
|
|
287
|
-
description: "Filename for contentBase64 content (drives the key and content type). With `file`, overrides the key's leaf (clean name) while keeping the pr/default path.",
|
|
285
|
+
description: "Filename for contentBase64 content (drives the key and content type). With single `file`, overrides the key's leaf (clean name) while keeping the pr/default path.",
|
|
288
286
|
},
|
|
289
287
|
key: {
|
|
290
288
|
type: "string",
|
|
291
|
-
description: "Explicit object key (default: <prefix>/<repo>/<ref>/<name>-<hash>.<ext>).
|
|
289
|
+
description: "Explicit object key (default: <prefix>/<repo>/<ref>/<name>-<hash>.<ext>). Single file only; cannot be combined with pr/issue.",
|
|
292
290
|
},
|
|
293
291
|
destination: {
|
|
294
292
|
type: "string",
|
|
@@ -308,7 +306,10 @@ export function createUploadsMcpTools(opts) {
|
|
|
308
306
|
type: "string",
|
|
309
307
|
description: "PR/issue/branch key segment (default: today, or UPLOADS_DEFAULT_REF). Cannot be combined with pr/issue.",
|
|
310
308
|
},
|
|
311
|
-
alt: {
|
|
309
|
+
alt: {
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "Alt text for the markdown (default: each file's name; with multiple files applies to all).",
|
|
312
|
+
},
|
|
312
313
|
width: {
|
|
313
314
|
type: "number",
|
|
314
315
|
description: "Emit <img width=…> markdown instead of a plain image embed.",
|
|
@@ -350,14 +351,24 @@ export function createUploadsMcpTools(opts) {
|
|
|
350
351
|
},
|
|
351
352
|
async handler(args) {
|
|
352
353
|
const file = optString(args, "file");
|
|
354
|
+
const filesArg = optStringArray(args, "files");
|
|
353
355
|
const contentBase64 = optString(args, "contentBase64");
|
|
354
|
-
if (
|
|
355
|
-
usage("
|
|
356
|
+
if (filesArg !== undefined && filesArg.length === 0) {
|
|
357
|
+
usage("files must be a non-empty array of paths");
|
|
358
|
+
}
|
|
359
|
+
const multi = filesArg !== undefined;
|
|
360
|
+
const sources = [file !== undefined, multi, contentBase64 !== undefined];
|
|
361
|
+
if (sources.filter(Boolean).length !== 1) {
|
|
362
|
+
usage("exactly one of file, files, or contentBase64 is required");
|
|
356
363
|
}
|
|
357
364
|
const filenameArg = optString(args, "filename");
|
|
358
365
|
if (contentBase64 !== undefined && !filenameArg) {
|
|
359
366
|
usage("filename is required with contentBase64");
|
|
360
367
|
}
|
|
368
|
+
if (multi && filenameArg)
|
|
369
|
+
usage("filename cannot be combined with files");
|
|
370
|
+
if (multi && optString(args, "key"))
|
|
371
|
+
usage("key cannot be combined with files");
|
|
361
372
|
const target = ghTargetFromArgs(args, run);
|
|
362
373
|
const wantComment = optBool(args, "comment");
|
|
363
374
|
const dryRun = optBool(args, "dryRun");
|
|
@@ -396,67 +407,130 @@ export function createUploadsMcpTools(opts) {
|
|
|
396
407
|
usage(err instanceof Error ? err.message : String(err));
|
|
397
408
|
}
|
|
398
409
|
const { client } = clientFor(args);
|
|
399
|
-
const bytes = file !== undefined
|
|
400
|
-
? readFileArg(file)
|
|
401
|
-
: new Uint8Array(Buffer.from(contentBase64, "base64"));
|
|
402
|
-
const sourceName = file !== undefined ? (filenameArg ?? basename(file)) : filenameArg;
|
|
403
410
|
const defaults = resolvePutDefaults({ envFile: globals.envFile });
|
|
404
411
|
const frameOpts = mcpFrameOptions(args);
|
|
405
412
|
const optimizeOpts = mcpOptimizeOptions(args, defaults);
|
|
406
|
-
const prepared = await prepareImageForUpload(bytes, sourceName, {
|
|
407
|
-
...frameOpts,
|
|
408
|
-
optimize: optimizeOpts,
|
|
409
|
-
});
|
|
410
|
-
const filename = prepared.filename;
|
|
411
|
-
let key = target ? ghAttachmentKey(target, filename) : keyArg;
|
|
412
|
-
if (key && prepared.optimized)
|
|
413
|
-
key = rewriteKeyExtension(key, filename);
|
|
414
413
|
const noGit = optBool(args, "noGit") || defaults.noGit === true;
|
|
415
|
-
const
|
|
416
|
-
|
|
417
|
-
|
|
414
|
+
const alt = optString(args, "alt");
|
|
415
|
+
const width = optPosInt(args, "width") ?? defaults.width;
|
|
416
|
+
const contentType = optString(args, "contentType");
|
|
417
|
+
const putShared = {
|
|
418
|
+
client,
|
|
419
|
+
ghTarget: target,
|
|
418
420
|
prefix: resolvedPrefix ?? defaults.prefix,
|
|
419
421
|
repo: optString(args, "repo") ?? defaults.repo,
|
|
420
422
|
ref: refArg ?? defaults.ref,
|
|
421
|
-
contentType: prepared.optimized ? prepared.contentType : optString(args, "contentType"),
|
|
422
423
|
deriveRepoFromGit: !noGit,
|
|
424
|
+
contentType,
|
|
423
425
|
dryRun,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
client: "uploads-mcp",
|
|
427
|
-
optimized: prepared.optimized,
|
|
428
|
-
frameId: prepared.frame?.framed ? prepared.frame.frameId : undefined,
|
|
429
|
-
keepExif: optimizeOpts.keepExif === true,
|
|
430
|
-
}),
|
|
426
|
+
optimize: optimizeOpts,
|
|
427
|
+
frame: frameOpts,
|
|
431
428
|
metadata,
|
|
429
|
+
provenanceClient: "uploads-mcp",
|
|
430
|
+
alt,
|
|
431
|
+
width,
|
|
432
|
+
};
|
|
433
|
+
// Multi-file path (paths only — no base64 batch).
|
|
434
|
+
if (multi) {
|
|
435
|
+
const { uploads, failures } = await uploadPuts({
|
|
436
|
+
...putShared,
|
|
437
|
+
files: filesArg,
|
|
438
|
+
});
|
|
439
|
+
if (uploads.length === 0 && failures.length > 0) {
|
|
440
|
+
throw new ToolBatchError(batchFailureMessage(failures), { uploads, failures });
|
|
441
|
+
}
|
|
442
|
+
if (wantComment && target && uploads.length > 0) {
|
|
443
|
+
const { comment, commentError } = await syncComment(client, target);
|
|
444
|
+
return { uploads, failures, comment, commentError };
|
|
445
|
+
}
|
|
446
|
+
return { uploads, failures };
|
|
447
|
+
}
|
|
448
|
+
// Single-file: contentBase64 still supported; paths go through uploadPuts.
|
|
449
|
+
if (contentBase64 !== undefined) {
|
|
450
|
+
const sourceName = filenameArg;
|
|
451
|
+
const bytes = new Uint8Array(Buffer.from(contentBase64, "base64"));
|
|
452
|
+
const prepared = await prepareImageForUpload(bytes, sourceName, {
|
|
453
|
+
...frameOpts,
|
|
454
|
+
optimize: optimizeOpts,
|
|
455
|
+
});
|
|
456
|
+
const filename = prepared.filename;
|
|
457
|
+
let key = target ? ghAttachmentKey(target, filename) : keyArg;
|
|
458
|
+
if (key && prepared.optimized)
|
|
459
|
+
key = rewriteKeyExtension(key, filename);
|
|
460
|
+
const result = await client.put(prepared.bytes, {
|
|
461
|
+
filename,
|
|
462
|
+
key,
|
|
463
|
+
prefix: resolvedPrefix ?? defaults.prefix,
|
|
464
|
+
repo: optString(args, "repo") ?? defaults.repo,
|
|
465
|
+
ref: refArg ?? defaults.ref,
|
|
466
|
+
contentType: prepared.optimized ? prepared.contentType : contentType,
|
|
467
|
+
deriveRepoFromGit: !noGit,
|
|
468
|
+
dryRun,
|
|
469
|
+
provenance: buildCliProvenance({
|
|
470
|
+
sourceName,
|
|
471
|
+
client: "uploads-mcp",
|
|
472
|
+
optimized: prepared.optimized,
|
|
473
|
+
frameId: prepared.frame?.framed ? prepared.frame.frameId : undefined,
|
|
474
|
+
keepExif: optimizeOpts.keepExif === true,
|
|
475
|
+
}),
|
|
476
|
+
metadata,
|
|
477
|
+
});
|
|
478
|
+
const markdown = buildMarkdown(urlForGithubEmbed(result.url, result.embedUrl), {
|
|
479
|
+
alt: alt ?? sourceName,
|
|
480
|
+
width,
|
|
481
|
+
});
|
|
482
|
+
const optimize = {
|
|
483
|
+
optimized: prepared.optimized,
|
|
484
|
+
skippedReason: prepared.skippedReason,
|
|
485
|
+
originalBytes: prepared.originalBytes,
|
|
486
|
+
outputBytes: prepared.outputBytes,
|
|
487
|
+
filename: prepared.filename,
|
|
488
|
+
};
|
|
489
|
+
if (wantComment && target) {
|
|
490
|
+
const { comment, commentError } = await syncComment(client, target);
|
|
491
|
+
return { ...result, markdown, optimize, frame: prepared.frame, comment, commentError };
|
|
492
|
+
}
|
|
493
|
+
return {
|
|
494
|
+
...result,
|
|
495
|
+
markdown,
|
|
496
|
+
optimize,
|
|
497
|
+
frame: prepared.frame,
|
|
498
|
+
...(dryRun ? { dryRun: true } : {}),
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
const { uploads, failures, firstError } = await uploadPuts({
|
|
502
|
+
...putShared,
|
|
503
|
+
files: [file],
|
|
504
|
+
nameOverride: filenameArg,
|
|
505
|
+
explicitKey: keyArg,
|
|
432
506
|
});
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
507
|
+
if (uploads.length === 0 && failures.length > 0) {
|
|
508
|
+
throw firstError instanceof Error ? firstError : new Error(String(firstError));
|
|
509
|
+
}
|
|
510
|
+
const u = uploads[0];
|
|
511
|
+
const flat = {
|
|
512
|
+
workspace: u.workspace,
|
|
513
|
+
key: u.key,
|
|
514
|
+
url: u.url,
|
|
515
|
+
embedUrl: u.embedUrl,
|
|
516
|
+
size: u.size,
|
|
517
|
+
contentType: u.contentType,
|
|
518
|
+
replaced: u.replaced,
|
|
519
|
+
markdown: u.markdown,
|
|
520
|
+
optimize: u.optimize,
|
|
521
|
+
frame: u.frame,
|
|
522
|
+
...(dryRun ? { dryRun: true } : {}),
|
|
443
523
|
};
|
|
444
524
|
if (wantComment && target) {
|
|
445
525
|
const { comment, commentError } = await syncComment(client, target);
|
|
446
|
-
return { ...
|
|
526
|
+
return { ...flat, comment, commentError };
|
|
447
527
|
}
|
|
448
|
-
return
|
|
449
|
-
...result,
|
|
450
|
-
markdown,
|
|
451
|
-
optimize,
|
|
452
|
-
frame: prepared.frame,
|
|
453
|
-
...(dryRun ? { dryRun: true } : {}),
|
|
454
|
-
};
|
|
528
|
+
return flat;
|
|
455
529
|
},
|
|
456
530
|
},
|
|
457
531
|
{
|
|
458
532
|
name: "attach",
|
|
459
|
-
description: "Upload one or more files as stable PR/issue attachments and maintain a
|
|
533
|
+
description: "Upload one or more files as stable PR/issue attachments (in parallel) and maintain a managed GitHub comment. Returns `uploads` and `failures` (one bad file does not abort the batch). Each success has `url`, `embedUrl`, and `markdown` (prefer embedUrl for GitHub). With no pr/issue, targets the current branch PR. Attachments are public and keys are predictable; upload only non-sensitive media.",
|
|
460
534
|
inputSchema: {
|
|
461
535
|
type: "object",
|
|
462
536
|
properties: {
|
|
@@ -523,45 +597,28 @@ export function createUploadsMcpTools(opts) {
|
|
|
523
597
|
const metadata = { ...metaExtras, ...ghMetadataFromTarget(target) };
|
|
524
598
|
if (Object.keys(metadata).length > 0)
|
|
525
599
|
validateMetaMap(metadata);
|
|
526
|
-
const uploads =
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
keepExif: optimizeOpts.keepExif === true,
|
|
543
|
-
}),
|
|
544
|
-
metadata,
|
|
545
|
-
});
|
|
546
|
-
uploads.push({
|
|
547
|
-
...result,
|
|
548
|
-
markdown: buildMarkdown(urlForGithubEmbed(result.url, result.embedUrl), {
|
|
549
|
-
alt: sourceName,
|
|
550
|
-
}),
|
|
551
|
-
frame: prepared.frame,
|
|
552
|
-
optimize: {
|
|
553
|
-
optimized: prepared.optimized,
|
|
554
|
-
skippedReason: prepared.skippedReason,
|
|
555
|
-
originalBytes: prepared.originalBytes,
|
|
556
|
-
outputBytes: prepared.outputBytes,
|
|
557
|
-
filename: prepared.filename,
|
|
558
|
-
},
|
|
600
|
+
const { uploads, failures } = await uploadAttachments({
|
|
601
|
+
client,
|
|
602
|
+
target,
|
|
603
|
+
files,
|
|
604
|
+
contentType,
|
|
605
|
+
optimize: optimizeOpts,
|
|
606
|
+
frame: frameOpts,
|
|
607
|
+
metadata,
|
|
608
|
+
provenanceClient: "uploads-mcp",
|
|
609
|
+
});
|
|
610
|
+
// Total failure → isError with full failures[] for agents.
|
|
611
|
+
if (uploads.length === 0 && failures.length > 0) {
|
|
612
|
+
throw new ToolBatchError(batchFailureMessage(failures), {
|
|
613
|
+
target,
|
|
614
|
+
uploads,
|
|
615
|
+
failures,
|
|
559
616
|
});
|
|
560
617
|
}
|
|
561
618
|
if (optBool(args, "noComment"))
|
|
562
|
-
return { target, uploads };
|
|
619
|
+
return { target, uploads, failures };
|
|
563
620
|
const { comment, commentError } = await syncComment(client, target);
|
|
564
|
-
return { target, uploads, comment, commentError };
|
|
621
|
+
return { target, uploads, failures, comment, commentError };
|
|
565
622
|
},
|
|
566
623
|
},
|
|
567
624
|
{
|
|
@@ -628,9 +685,28 @@ export function createUploadsMcpTools(opts) {
|
|
|
628
685
|
return client.delete(key);
|
|
629
686
|
},
|
|
630
687
|
},
|
|
688
|
+
{
|
|
689
|
+
name: "get_metadata",
|
|
690
|
+
description: "Read an object's queryable custom metadata (D1 key-value pairs, not R2 provenance). Returns `{ metadata }` (empty when none). Object must exist. Same as `uploads meta get`.",
|
|
691
|
+
inputSchema: {
|
|
692
|
+
type: "object",
|
|
693
|
+
properties: {
|
|
694
|
+
key: { type: "string", description: "Object key to inspect." },
|
|
695
|
+
workspace: workspaceProp,
|
|
696
|
+
},
|
|
697
|
+
required: ["key"],
|
|
698
|
+
additionalProperties: false,
|
|
699
|
+
},
|
|
700
|
+
async handler(args) {
|
|
701
|
+
const key = optString(args, "key");
|
|
702
|
+
if (!key)
|
|
703
|
+
usage("key is required");
|
|
704
|
+
return clientFor(args).client.getMetadata(key);
|
|
705
|
+
},
|
|
706
|
+
},
|
|
631
707
|
{
|
|
632
708
|
name: "set_metadata",
|
|
633
|
-
description: "Merge-set and/or delete an object's queryable custom metadata (D1
|
|
709
|
+
description: "Merge-set and/or delete an object's queryable custom metadata (D1 key-value pairs, not R2 provenance). `set` wins over `delete` for the same key. " +
|
|
634
710
|
METADATA_DESCRIPTION +
|
|
635
711
|
" Requires at least one of `set` or `delete`. Same as `uploads meta set`.",
|
|
636
712
|
inputSchema: {
|