@buildinternet/uploads 0.48.1 → 0.50.0

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.
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Fetch bytes from a caller-supplied URL for `put --url` / MCP `contentUrl`.
3
+ *
4
+ * Guardrails: HTTPS only (http allowed on loopback when `allowLoopback` is
5
+ * set), no URL credentials, private/internal hosts rejected unless they are
6
+ * loopback and `allowLoopback` is set. Redirects re-checked each hop; a
7
+ * public origin cannot redirect onto loopback. No auth headers forwarded.
8
+ * The server still sniffs and size-caps after this.
9
+ */
10
+ import { UploadsError } from "./errors.js";
11
+ import { isLoopbackHost, isPrivateOrLocalHost } from "./private-host.js";
12
+ export const FETCH_UPLOAD_SOURCE_TIMEOUT_MS = 15_000;
13
+ export const FETCH_UPLOAD_SOURCE_MAX_REDIRECTS = 5;
14
+ /** Client-side cap when the caller does not pass a workspace policy ceiling. */
15
+ export const FETCH_UPLOAD_SOURCE_DEFAULT_MAX_BYTES = 25 * 1024 * 1024;
16
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
17
+ function fail(label, message, code = "USAGE") {
18
+ throw new UploadsError(`${label} ${message}`, code);
19
+ }
20
+ /** Parse and reject URLs we will not fetch. Used on the original URL and every redirect. */
21
+ export function assertFetchableUploadUrl(raw, label = "url", opts = {}) {
22
+ let url;
23
+ try {
24
+ url = new URL(raw);
25
+ }
26
+ catch {
27
+ fail(label, "must be a valid absolute URL");
28
+ }
29
+ const loopback = isLoopbackHost(url.hostname);
30
+ const allowThisLoopback = Boolean(opts.allowLoopback && loopback);
31
+ if (url.protocol === "http:") {
32
+ if (!allowThisLoopback)
33
+ fail(label, "must be https");
34
+ }
35
+ else if (url.protocol !== "https:") {
36
+ fail(label, "must be https");
37
+ }
38
+ if (url.username !== "" || url.password !== "") {
39
+ fail(label, "must not include credentials");
40
+ }
41
+ if (isPrivateOrLocalHost(url.hostname) && !allowThisLoopback) {
42
+ fail(label, "targets a private or internal network");
43
+ }
44
+ return url;
45
+ }
46
+ /** Filename leaf from a URL path (`https://cdn.example/a/shot.png?x=1` → `shot.png`). */
47
+ export function filenameFromUploadUrl(url) {
48
+ const last = url.pathname.replace(/\/+$/, "").split("/").pop();
49
+ if (!last)
50
+ return undefined;
51
+ let decoded = last;
52
+ try {
53
+ decoded = decodeURIComponent(last);
54
+ }
55
+ catch {
56
+ // Keep the raw segment.
57
+ }
58
+ const cleaned = decoded.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
59
+ return cleaned || undefined;
60
+ }
61
+ /** `filename` if given, else the URL path leaf. Throws USAGE when neither works. */
62
+ export function resolveUploadFilename(rawUrl, filename, label = "url", opts = {}) {
63
+ if (filename)
64
+ return filename;
65
+ const derived = filenameFromUploadUrl(assertFetchableUploadUrl(rawUrl, label, opts));
66
+ if (!derived) {
67
+ throw new UploadsError(`${label} has no filename in the path; pass a filename`, "USAGE");
68
+ }
69
+ return derived;
70
+ }
71
+ function timeoutError(label) {
72
+ fail(label, "fetch timed out", "NETWORK");
73
+ }
74
+ function isAbortError(err) {
75
+ return ((err instanceof DOMException && err.name === "AbortError") ||
76
+ (err instanceof Error && err.name === "AbortError"));
77
+ }
78
+ async function readCappedBody(res, maxBytes, label) {
79
+ const declared = Number(res.headers.get("content-length"));
80
+ if (Number.isFinite(declared) && declared > maxBytes) {
81
+ fail(label, `exceeds the upload limit (${maxBytes} bytes)`);
82
+ }
83
+ const body = res.body;
84
+ if (!body)
85
+ fail(label, "returned an empty body");
86
+ const reader = body.getReader();
87
+ const chunks = [];
88
+ let total = 0;
89
+ try {
90
+ for (;;) {
91
+ const { done, value } = await reader.read();
92
+ if (done)
93
+ break;
94
+ if (!value || value.byteLength === 0)
95
+ continue;
96
+ total += value.byteLength;
97
+ if (total > maxBytes) {
98
+ await reader.cancel().catch(() => undefined);
99
+ fail(label, `exceeds the upload limit (${maxBytes} bytes)`);
100
+ }
101
+ chunks.push(value);
102
+ }
103
+ }
104
+ finally {
105
+ try {
106
+ reader.releaseLock();
107
+ }
108
+ catch {
109
+ // Already locked/cancelled after a size abort.
110
+ }
111
+ }
112
+ if (total === 0)
113
+ fail(label, "returned an empty body");
114
+ if (chunks.length === 1)
115
+ return chunks[0];
116
+ const out = new Uint8Array(total);
117
+ let offset = 0;
118
+ for (const chunk of chunks) {
119
+ out.set(chunk, offset);
120
+ offset += chunk.byteLength;
121
+ }
122
+ return out;
123
+ }
124
+ /**
125
+ * GET `url` and return the body bytes, capped at `maxBytes`.
126
+ *
127
+ * Redirects are followed manually so each hop is re-validated (scheme, no
128
+ * credentials, host policy). A public origin cannot redirect onto loopback
129
+ * even when `allowLoopback` is set. Auth headers are never forwarded.
130
+ */
131
+ export async function fetchUploadSource(raw, opts = {}) {
132
+ const label = opts.label ?? "url";
133
+ const timeoutMs = opts.timeoutMs ?? FETCH_UPLOAD_SOURCE_TIMEOUT_MS;
134
+ const maxBytes = opts.maxBytes ?? FETCH_UPLOAD_SOURCE_DEFAULT_MAX_BYTES;
135
+ const doFetch = opts.fetch ?? fetch;
136
+ const timeout = AbortSignal.timeout(timeoutMs);
137
+ const signal = opts.signal ? AbortSignal.any([opts.signal, timeout]) : timeout;
138
+ const urlOpts = { allowLoopback: opts.allowLoopback };
139
+ let url = assertFetchableUploadUrl(raw, label, urlOpts);
140
+ for (let hop = 0; hop <= FETCH_UPLOAD_SOURCE_MAX_REDIRECTS; hop++) {
141
+ let res;
142
+ try {
143
+ res = await doFetch(url, {
144
+ method: "GET",
145
+ redirect: "manual",
146
+ signal,
147
+ headers: {
148
+ accept: "*/*",
149
+ "user-agent": opts.userAgent ?? "uploads.sh",
150
+ },
151
+ });
152
+ }
153
+ catch (err) {
154
+ if (isAbortError(err) || timeout.aborted)
155
+ timeoutError(label);
156
+ throw new UploadsError(`could not fetch ${label}`, "NETWORK");
157
+ }
158
+ if (REDIRECT_STATUSES.has(res.status)) {
159
+ const location = res.headers.get("location");
160
+ if (!location)
161
+ fail(label, "redirect is missing a Location header");
162
+ if (hop === FETCH_UPLOAD_SOURCE_MAX_REDIRECTS) {
163
+ fail(label, "redirected too many times");
164
+ }
165
+ // Loopback is only sticky while we are already on loopback. A public
166
+ // CDN cannot bounce the CLI onto http://127.0.0.1.
167
+ url = assertFetchableUploadUrl(new URL(location, url).toString(), label, {
168
+ allowLoopback: urlOpts.allowLoopback && isLoopbackHost(url.hostname),
169
+ });
170
+ continue;
171
+ }
172
+ if (res.status !== 200) {
173
+ throw new UploadsError(`could not fetch ${label} (HTTP ${res.status})`, "NETWORK");
174
+ }
175
+ try {
176
+ return await readCappedBody(res, maxBytes, label);
177
+ }
178
+ catch (err) {
179
+ if (isAbortError(err) || timeout.aborted)
180
+ timeoutError(label);
181
+ throw err;
182
+ }
183
+ }
184
+ fail(label, "redirected too many times");
185
+ }
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export { sanitizeKeySegment, sha256Short, deriveRepoFromGit, buildScreenshotKey
4
4
  export { BUILTIN_DESTINATIONS, isBuiltinDestination, keyMatchesDestination, resolveDestinationRoot, resolvePutPrefix, type BuiltinDestinationId, } from "./destinations.js";
5
5
  export { DEFAULT_API_URL, DEFAULT_WORKSPACE, UPLOADS_CONFIG_KEYS, defaultConfigPath, resolveConfigPath, loadConfigFile, loadEnvFile, resolveApiUrl, resolveConfig, describeConfigSources, redactToken, writeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, workspaceFromToken, workspaceMismatch, type UploadsClientConfig, type ResolvedConfig, type WorkspaceSource, type ConfigValueSource, type ConfigSources, type UploadsConfigKey, type UploadsConfigValues, type PutDefaults, } from "./config.js";
6
6
  export { UploadsError, type UploadsErrorCode } from "./errors.js";
7
+ export { assertFetchableUploadUrl, fetchUploadSource, filenameFromUploadUrl, resolveUploadFilename, FETCH_UPLOAD_SOURCE_DEFAULT_MAX_BYTES, FETCH_UPLOAD_SOURCE_MAX_REDIRECTS, FETCH_UPLOAD_SOURCE_TIMEOUT_MS, } from "./fetch-upload-source.js";
7
8
  export { createUploadsClient, type UploadsClient, type PutOptions, type ProvenanceInput, type ListOptions, type PutResult, type ListItem, type ListResult, type HeadResult, type DeleteResult, type GalleryItem, type Gallery, type GallerySummary, type GalleryListOptions, type GalleryListResult, type CreateGalleryOptions, type AddGalleryItemOptions, type DeleteGalleryOptions, type HealthResult, type UsageResult, type ReconcileResult, type PurgeExpiredResult, type PurgeExpiredResponse, type FindFilesOptions, type FindFilesItem, type FindFilesResult, type MetadataKeysResult, type MetadataValuesResult, type GetMetadataResult, type PatchMetadataOptions, type ResolveGhPrefixOptions, type ResolveGhPrefixResult, } from "./client.js";
8
9
  export { buildCliProvenance } from "./provenance.js";
9
10
  export { META_KEY_RE, META_VALUE_MAX, META_MAX_KEYS, META_MAX_TOTAL_BYTES, validateMetaEntry, parseMetaPair, parseMetaFlags, } from "./metadata.js";
package/dist/index.js CHANGED
@@ -4,6 +4,7 @@ export { sanitizeKeySegment, sha256Short, deriveRepoFromGit, buildScreenshotKey
4
4
  export { BUILTIN_DESTINATIONS, isBuiltinDestination, keyMatchesDestination, resolveDestinationRoot, resolvePutPrefix, } from "./destinations.js";
5
5
  export { DEFAULT_API_URL, DEFAULT_WORKSPACE, UPLOADS_CONFIG_KEYS, defaultConfigPath, resolveConfigPath, loadConfigFile, loadEnvFile, resolveApiUrl, resolveConfig, describeConfigSources, redactToken, writeConfigKeys, configValuesFromClient, putDefaultsToConfigValues, resolvePutDefaults, mergePutDefaults, workspaceFromToken, workspaceMismatch, } from "./config.js";
6
6
  export { UploadsError } from "./errors.js";
7
+ export { assertFetchableUploadUrl, fetchUploadSource, filenameFromUploadUrl, resolveUploadFilename, FETCH_UPLOAD_SOURCE_DEFAULT_MAX_BYTES, FETCH_UPLOAD_SOURCE_MAX_REDIRECTS, FETCH_UPLOAD_SOURCE_TIMEOUT_MS, } from "./fetch-upload-source.js";
7
8
  export { createUploadsClient, } from "./client.js";
8
9
  export { buildCliProvenance } from "./provenance.js";
9
10
  export { META_KEY_RE, META_VALUE_MAX, META_MAX_KEYS, META_MAX_TOTAL_BYTES, validateMetaEntry, parseMetaPair, parseMetaFlags, } from "./metadata.js";
@@ -10,12 +10,10 @@ export declare function optPosInt(args: ToolArgs, name: string, options?: {
10
10
  export declare function optStringRecord(args: ToolArgs, name: string): Record<string, string> | undefined;
11
11
  /** A JSON-array argument of strings (e.g. a `delete` or `files` param). */
12
12
  export declare function optStringArray(args: ToolArgs, name: string): string[] | undefined;
13
- /**
14
- * Shared tool-description text for the metadata-shaped `metadata`/`set`/
15
- * `filters` params across the CLI/local MCP (put/attach/set_metadata/
16
- * find_files) and the remote MCP worker (set_metadata/find_files).
17
- */
18
- export declare const METADATA_DESCRIPTION = "Queryable custom metadata (key\u2192value), separate from provenance. Omit to leave any metadata already stored for this key untouched; pass an object (even {}) to fully replace it. Keys: lowercase, ^[a-z][a-z0-9._-]{0,63}$. Values: 1-512 printable ASCII characters. Caps: at most 24 keys, at most 8192 total key+value bytes. Canonical keys, which uploads.sh derives automatically where it can: url, path, env, theme, viewport, device, software, captured. Use `path` for the route (e.g. /settings) \u2014 that is the key `find_files` searches by, so spell it `path` and not route/page/screen. `gh.*` is reserved by convention for GitHub PR/issue attachment context (repo/kind/number/ref).";
13
+ /** Shared cue: `path` is the route `find_files` searches by. */
14
+ export declare const METADATA_PATH_CUE = "Use `path` for the route (e.g. /settings), not route/page/screen.";
15
+ /** put/screenshot/attach `metadata`. Key regex and caps stay in usage errors. */
16
+ export declare const METADATA_DESCRIPTION: string;
19
17
  export declare const metadataProp: {
20
18
  type: string;
21
19
  additionalProperties: {
@@ -23,6 +21,8 @@ export declare const metadataProp: {
23
21
  };
24
22
  description: string;
25
23
  };
24
+ /** 1×1 PNG, used only in MCP `inputSchema.examples` so copy-paste from Inspector works. */
25
+ export declare const MCP_EXAMPLE_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
26
26
  export declare const stateProp: {
27
27
  type: string;
28
28
  enum: ("after" | "before" | "empty" | "error" | "loading")[];
package/dist/mcp/args.js CHANGED
@@ -66,17 +66,19 @@ export function optStringArray(args, name) {
66
66
  }
67
67
  return v;
68
68
  }
69
- /**
70
- * Shared tool-description text for the metadata-shaped `metadata`/`set`/
71
- * `filters` params across the CLI/local MCP (put/attach/set_metadata/
72
- * find_files) and the remote MCP worker (set_metadata/find_files).
73
- */
74
- export const METADATA_DESCRIPTION = "Queryable custom metadata (key→value), separate from provenance. Omit to leave any metadata already stored for this key untouched; pass an object (even {}) to fully replace it. Keys: lowercase, ^[a-z][a-z0-9._-]{0,63}$. Values: 1-512 printable ASCII characters. Caps: at most 24 keys, at most 8192 total key+value bytes. Canonical keys, which uploads.sh derives automatically where it can: url, path, env, theme, viewport, device, software, captured. Use `path` for the route (e.g. /settings) — that is the key `find_files` searches by, so spell it `path` and not route/page/screen. `gh.*` is reserved by convention for GitHub PR/issue attachment context (repo/kind/number/ref).";
69
+ /** Shared cue: `path` is the route `find_files` searches by. */
70
+ export const METADATA_PATH_CUE = "Use `path` for the route (e.g. /settings), not route/page/screen.";
71
+ /** put/screenshot/attach `metadata`. Key regex and caps stay in usage errors. */
72
+ export const METADATA_DESCRIPTION = "Queryable tags for later search (key→value). " +
73
+ METADATA_PATH_CUE +
74
+ " Omit to leave existing tags; pass an object (even {}) to replace them. `state` and `app` have their own fields.";
75
75
  export const metadataProp = {
76
76
  type: "object",
77
77
  additionalProperties: { type: "string" },
78
78
  description: METADATA_DESCRIPTION,
79
79
  };
80
+ /** 1×1 PNG, used only in MCP `inputSchema.examples` so copy-paste from Inspector works. */
81
+ export const MCP_EXAMPLE_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==";
80
82
  export const stateProp = {
81
83
  type: "string",
82
84
  enum: [...META_STATE_VALUES],
@@ -20,7 +20,7 @@ export declare const repoLinkStatusResultSchema: JsonSchema;
20
20
  export declare const usageResultSchema: JsonSchema;
21
21
  export declare const reconcileResultSchema: JsonSchema;
22
22
  export declare const purgeExpiredResultSchema: JsonSchema;
23
- export declare const healthResultSchema: JsonSchema;
23
+ export declare const whoamiResultSchema: JsonSchema;
24
24
  export declare const promoteToolResultSchema: JsonSchema;
25
25
  export declare const galleryResultSchema: JsonSchema;
26
26
  export declare const galleryFindResultSchema: JsonSchema;
@@ -191,13 +191,30 @@ export const usageResultSchema = objectSchema({
191
191
  workspace: { type: "string" },
192
192
  bytes: { type: "number" },
193
193
  objects: { type: "number" },
194
+ sharedBytes: { type: "number" },
195
+ sharedObjects: { type: "number" },
194
196
  uploadsInPeriod: { type: "number" },
195
197
  periodStart: { type: "string" },
196
198
  updatedAt: { type: "string" },
199
+ storageBudgetBasis: { type: "string", enum: ["total", "shared"] },
197
200
  maxStorageBytes: { type: "number" },
198
201
  storageRemainingBytes: { type: "number" },
199
202
  maxUploadsPerPeriod: { type: "number" },
200
203
  uploadsRemaining: { type: "number" },
204
+ // GET /:workspace/usage (routes/workspace-usage.ts) also stamps these on
205
+ // every response — not part of `usageWithLimits`'s return, so easy to miss.
206
+ scopes: { type: "array", items: { type: "string" } },
207
+ plan: { type: "string" },
208
+ storage: objectSchema({
209
+ mode: { type: "string", enum: ["shared", "byo"] },
210
+ fallbackLanes: { type: "number" },
211
+ health: objectSchema({
212
+ ok: { type: "boolean" },
213
+ code: { type: "string" },
214
+ message: { type: "string" },
215
+ since: { type: "string" },
216
+ }, ["ok"]),
217
+ }, ["mode", "fallbackLanes", "health"]),
201
218
  });
202
219
  export const reconcileResultSchema = objectSchema({
203
220
  workspace: { type: "string" },
@@ -223,10 +240,14 @@ export const purgeExpiredResultSchema = objectSchema({
223
240
  keysTruncated: { type: "boolean" },
224
241
  reconcile: reconcileResultSchema,
225
242
  });
226
- export const healthResultSchema = objectSchema({
243
+ export const whoamiResultSchema = objectSchema({
227
244
  ok: { type: "boolean" },
245
+ workspace: { type: "string" },
246
+ scopes: { type: "array", items: { type: "string" } },
247
+ userId: nullableString,
248
+ signedIn: { type: "boolean" },
228
249
  apiUrl: { type: "string" },
229
- });
250
+ }, ["ok", "workspace"]);
230
251
  export const promoteToolResultSchema = objectSchema({
231
252
  // `promotion` is optional (issue #702): a `keys`-only call (no `branch`)
232
253
  // never runs the branch sweep, so there's nothing to report under it.
@@ -307,7 +328,7 @@ export const hostedOutputSchemas = {
307
328
  usage: usageResultSchema,
308
329
  reconcile: reconcileResultSchema,
309
330
  purge_expired: purgeExpiredResultSchema,
310
- health: healthResultSchema,
331
+ whoami: whoamiResultSchema,
311
332
  };
312
333
  /** Shared-shape stdio tools. Hosted-only tools (`promote`, `repo_link_status`) omitted. */
313
334
  export const stdioOutputSchemas = {
@@ -340,7 +361,7 @@ export const stdioOutputSchemas = {
340
361
  usage: usageResultSchema,
341
362
  reconcile: reconcileResultSchema,
342
363
  purge_expired: purgeExpiredResultSchema,
343
- health: healthResultSchema,
364
+ whoami: whoamiResultSchema,
344
365
  report: objectSchema({
345
366
  ok: { type: "boolean" },
346
367
  id: { type: "string" },
@@ -18,18 +18,18 @@
18
18
  * must never go to stdout.
19
19
  */
20
20
  import { McpServer, type Implementation, type jsonSchemaValidator } from "@modelcontextprotocol/server";
21
- export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, type ToolArgs, } from "./args.js";
21
+ export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, METADATA_PATH_CUE, MCP_EXAMPLE_PNG_BASE64, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, type ToolArgs, } from "./args.js";
22
22
  export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
23
23
  export { mapBounded } from "../async.js";
24
24
  export { McpServer, type jsonSchemaValidator };
25
- export { commentResultSchema, deleteResultSchema, findFilesResultSchema, galleryFindResultSchema, galleryResultSchema, healthResultSchema, hostedOutputSchemas, listResultSchema, metadataFacetsResultSchema, metadataResultSchema, promoteToolResultSchema, purgeExpiredResultSchema, putResultSchema, reconcileResultSchema, repoLinkStatusResultSchema, stdioOutputSchemas, usageResultSchema, withOutputSchemas, } from "./output-schemas.js";
25
+ export { commentResultSchema, deleteResultSchema, findFilesResultSchema, galleryFindResultSchema, galleryResultSchema, hostedOutputSchemas, listResultSchema, metadataFacetsResultSchema, metadataResultSchema, promoteToolResultSchema, purgeExpiredResultSchema, putResultSchema, reconcileResultSchema, repoLinkStatusResultSchema, stdioOutputSchemas, usageResultSchema, withOutputSchemas, } from "./output-schemas.js";
26
26
  /** MCP tool safety hints. Required so tools/list advertises them for review. */
27
27
  export interface McpToolAnnotations {
28
28
  readOnlyHint: boolean;
29
29
  destructiveHint: boolean;
30
30
  openWorldHint: boolean;
31
31
  }
32
- /** Lookup / list / health. Does not change workspace or public state. */
32
+ /** Lookup / list / whoami. Does not change workspace or public state. */
33
33
  export declare const mcpRead: McpToolAnnotations;
34
34
  /** Creates or updates a public object, gallery, or comment without deleting. */
35
35
  export declare const mcpWritePublic: McpToolAnnotations;
@@ -50,9 +50,9 @@ export type McpSecurityScheme = {
50
50
  export declare const mcpOAuthRead: McpSecurityScheme[];
51
51
  export declare const mcpOAuthWrite: McpSecurityScheme[];
52
52
  export declare const mcpOAuthDelete: McpSecurityScheme[];
53
- /** Authenticated, no particular file scope (hosted `health`). */
53
+ /** Authenticated, no particular file scope (hosted `whoami`). */
54
54
  export declare const mcpOAuthAny: McpSecurityScheme[];
55
- /** Callable without a token (stdio `health`). */
55
+ /** Callable without a token (stdio `whoami` when unsigned-in). */
56
56
  export declare const mcpNoAuth: McpSecurityScheme[];
57
57
  /**
58
58
  * Thrown when a presented token is missing a required scope. wrapHandler
@@ -21,12 +21,12 @@ import { fromJsonSchema, McpServer, } from "@modelcontextprotocol/server";
21
21
  import { UploadsError } from "../errors.js";
22
22
  import { errorCodeFromUnknown, recordEvent } from "../telemetry.js";
23
23
  import { ToolBatchError } from "./batch-error.js";
24
- export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, } from "./args.js";
24
+ export { appProp, canonicalMetaFromArgs, METADATA_DESCRIPTION, METADATA_PATH_CUE, MCP_EXAMPLE_PNG_BASE64, metadataArgWithCanonical, metadataProp, stateProp, optBool, optPosInt, optString, optStringArray, optStringRecord, usage, } from "./args.js";
25
25
  export { ToolBatchError, batchFailureMessage } from "./batch-error.js";
26
26
  export { mapBounded } from "../async.js";
27
27
  export { McpServer };
28
- export { commentResultSchema, deleteResultSchema, findFilesResultSchema, galleryFindResultSchema, galleryResultSchema, healthResultSchema, hostedOutputSchemas, listResultSchema, metadataFacetsResultSchema, metadataResultSchema, promoteToolResultSchema, purgeExpiredResultSchema, putResultSchema, reconcileResultSchema, repoLinkStatusResultSchema, stdioOutputSchemas, usageResultSchema, withOutputSchemas, } from "./output-schemas.js";
29
- /** Lookup / list / health. Does not change workspace or public state. */
28
+ export { commentResultSchema, deleteResultSchema, findFilesResultSchema, galleryFindResultSchema, galleryResultSchema, hostedOutputSchemas, listResultSchema, metadataFacetsResultSchema, metadataResultSchema, promoteToolResultSchema, purgeExpiredResultSchema, putResultSchema, reconcileResultSchema, repoLinkStatusResultSchema, stdioOutputSchemas, usageResultSchema, withOutputSchemas, } from "./output-schemas.js";
29
+ /** Lookup / list / whoami. Does not change workspace or public state. */
30
30
  export const mcpRead = {
31
31
  readOnlyHint: true,
32
32
  destructiveHint: false,
@@ -56,9 +56,9 @@ function oauth(scopes) {
56
56
  export const mcpOAuthRead = oauth(["files:read"]);
57
57
  export const mcpOAuthWrite = oauth(["files:write"]);
58
58
  export const mcpOAuthDelete = oauth(["files:delete"]);
59
- /** Authenticated, no particular file scope (hosted `health`). */
59
+ /** Authenticated, no particular file scope (hosted `whoami`). */
60
60
  export const mcpOAuthAny = oauth([]);
61
- /** Callable without a token (stdio `health`). */
61
+ /** Callable without a token (stdio `whoami` when unsigned-in). */
62
62
  export const mcpNoAuth = [{ type: "noauth" }];
63
63
  /**
64
64
  * Thrown when a presented token is missing a required scope. wrapHandler
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MCP tool set mirroring the CLI commands (put, attach, list, delete,
3
- * usage, reconcile, purge_expired, comment, health, doctor). Config is
3
+ * usage, reconcile, purge_expired, comment, whoami, doctor). Config is
4
4
  * resolved fresh per tool call so a
5
5
  * per-call `workspace` argument behaves like the CLI's --workspace flag, and
6
6
  * a missing token surfaces as a tool error rather than a startup failure.