@ethisyscore/extension-runtime 1.136.0 → 1.138.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.
Files changed (34) hide show
  1. package/README.md +2 -2
  2. package/dist/MockPluginStorage-DF3DL1dI.d.ts +59 -0
  3. package/dist/MockPluginStorage-QKfz8CMt.d.cts +59 -0
  4. package/dist/{bridge-client-RJOl8WlU.d.ts → bridge-client-CkxYcZcm.d.ts} +1 -1
  5. package/dist/{bridge-client-D9EYFq1l.d.cts → bridge-client-DkogtCvn.d.cts} +1 -1
  6. package/dist/{bridge-envelopes-M5a42rAy.d.cts → bridge-envelopes-BcKu-nQm.d.cts} +72 -1
  7. package/dist/{bridge-envelopes-M5a42rAy.d.ts → bridge-envelopes-BcKu-nQm.d.ts} +72 -1
  8. package/dist/host/index.cjs +84 -2
  9. package/dist/host/index.cjs.map +1 -1
  10. package/dist/host/index.d.cts +3 -3
  11. package/dist/host/index.d.ts +3 -3
  12. package/dist/host/index.js +84 -2
  13. package/dist/host/index.js.map +1 -1
  14. package/dist/mock-host/cli.cjs +340 -7
  15. package/dist/mock-host/cli.cjs.map +1 -1
  16. package/dist/mock-host/cli.d.cts +10 -3
  17. package/dist/mock-host/cli.d.ts +10 -3
  18. package/dist/mock-host/cli.js +339 -8
  19. package/dist/mock-host/cli.js.map +1 -1
  20. package/dist/mock-host/index.cjs +276 -1
  21. package/dist/mock-host/index.cjs.map +1 -1
  22. package/dist/mock-host/index.d.cts +14 -4
  23. package/dist/mock-host/index.d.ts +14 -4
  24. package/dist/mock-host/index.js +276 -2
  25. package/dist/mock-host/index.js.map +1 -1
  26. package/dist/plugin/index.cjs +407 -100
  27. package/dist/plugin/index.cjs.map +1 -1
  28. package/dist/plugin/index.d.cts +186 -6
  29. package/dist/plugin/index.d.ts +186 -6
  30. package/dist/plugin/index.js +395 -101
  31. package/dist/plugin/index.js.map +1 -1
  32. package/dist/{transport-UaoDRdm3.d.cts → transport-D1lE-5nt.d.cts} +30 -3
  33. package/dist/{transport-CFzJQiFG.d.ts → transport-INVtDo2s.d.ts} +30 -3
  34. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
1
  import { SduiNode } from '@ethisyscore/protocol';
2
- import { h as WorkerCtor, M as McpHttpClient, k as WorkerRemoteDomTransport } from '../transport-UaoDRdm3.cjs';
3
- import '../bridge-envelopes-M5a42rAy.cjs';
2
+ import { i as WorkerCtor, M as McpHttpClient, l as WorkerRemoteDomTransport } from '../transport-D1lE-5nt.cjs';
3
+ import { M as MockPluginStorage } from '../MockPluginStorage-QKfz8CMt.cjs';
4
+ import '../bridge-envelopes-BcKu-nQm.cjs';
4
5
 
5
6
  /**
6
7
  * `npx mock-host <dir>` — standalone declarative-mock-host dev server.
@@ -144,6 +145,12 @@ interface BootContractBHostOptions {
144
145
  */
145
146
  mcpClient?: McpHttpClient;
146
147
  }
148
+ /**
149
+ * Where the default mock client's `uploadToStorage` writes. Exported so a test or a dev script can
150
+ * read back what a surface uploaded.
151
+ */
152
+ declare const defaultMockPluginStorage: MockPluginStorage;
153
+ declare const defaultMockMcpClient: McpHttpClient;
147
154
  /**
148
155
  * Spawn the in-process worker that drives the Contract B mock host.
149
156
  *
@@ -171,4 +178,4 @@ declare function run(args: string[]): Promise<{
171
178
  close: () => Promise<void>;
172
179
  }>;
173
180
 
174
- export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
181
+ export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, defaultMockMcpClient, defaultMockPluginStorage, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
@@ -1,6 +1,7 @@
1
1
  import { SduiNode } from '@ethisyscore/protocol';
2
- import { h as WorkerCtor, M as McpHttpClient, k as WorkerRemoteDomTransport } from '../transport-CFzJQiFG.js';
3
- import '../bridge-envelopes-M5a42rAy.js';
2
+ import { i as WorkerCtor, M as McpHttpClient, l as WorkerRemoteDomTransport } from '../transport-INVtDo2s.js';
3
+ import { M as MockPluginStorage } from '../MockPluginStorage-DF3DL1dI.js';
4
+ import '../bridge-envelopes-BcKu-nQm.js';
4
5
 
5
6
  /**
6
7
  * `npx mock-host <dir>` — standalone declarative-mock-host dev server.
@@ -144,6 +145,12 @@ interface BootContractBHostOptions {
144
145
  */
145
146
  mcpClient?: McpHttpClient;
146
147
  }
148
+ /**
149
+ * Where the default mock client's `uploadToStorage` writes. Exported so a test or a dev script can
150
+ * read back what a surface uploaded.
151
+ */
152
+ declare const defaultMockPluginStorage: MockPluginStorage;
153
+ declare const defaultMockMcpClient: McpHttpClient;
147
154
  /**
148
155
  * Spawn the in-process worker that drives the Contract B mock host.
149
156
  *
@@ -171,4 +178,4 @@ declare function run(args: string[]): Promise<{
171
178
  close: () => Promise<void>;
172
179
  }>;
173
180
 
174
- export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
181
+ export { type BootContractBHostOptions, type ParsedArgs, type RenderMode, type ResourceMap, type ToolResultMap, bootContractBHost, defaultMockMcpClient, defaultMockPluginStorage, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
@@ -47,6 +47,29 @@ var MCP_ERROR_CODES = [
47
47
  function isMcpErrorCode(value) {
48
48
  return typeof value === "string" && MCP_ERROR_CODES.includes(value);
49
49
  }
50
+ var RETRYABLE = ["rate_limited", "timeout", "unavailable"];
51
+ function isRetryableMcpErrorCode(code) {
52
+ return RETRYABLE.includes(code);
53
+ }
54
+ var McpToolError = class extends Error {
55
+ /** Machine-readable classification. `internal` when the host sent none. */
56
+ code;
57
+ /**
58
+ * Marks the instance for {@link isMcpToolError}. A structural marker rather than a prototype
59
+ * check because the error can be constructed in one bundle and inspected in another, where
60
+ * `instanceof` compares two different class objects and answers false.
61
+ */
62
+ isMcpToolError = true;
63
+ constructor(message, code = "internal") {
64
+ super(message);
65
+ this.name = "McpHostError";
66
+ this.code = code;
67
+ }
68
+ /** True when this failure is worth retrying unchanged. */
69
+ get retryable() {
70
+ return isRetryableMcpErrorCode(this.code);
71
+ }
72
+ };
50
73
  function mcpErrorCodeFromHttpStatus(status) {
51
74
  switch (status) {
52
75
  case 401:
@@ -100,6 +123,157 @@ function classifyHostError(err) {
100
123
  return "internal";
101
124
  }
102
125
 
126
+ // src/plugin/pluginStorageUpload.ts
127
+ var PLUGIN_STORAGE_UPLOAD_MAX_BYTES = 30 * 1024 * 1024;
128
+ var PLUGIN_STORAGE_PREFIX_MAX_LENGTH = 84;
129
+ var PLUGIN_STORAGE_PATH_MAX_LENGTH = 128;
130
+ var PluginStorageUploadError = class extends McpToolError {
131
+ /** What went wrong, in terms a caller branches on. */
132
+ reason;
133
+ /** HTTP status from the server, when the host surfaced one. */
134
+ status;
135
+ /**
136
+ * Seconds the server asked the caller to wait (`Retry-After`), on a `rate_limited` failure
137
+ * when the host forwarded the header. Absent otherwise.
138
+ */
139
+ retryAfterSeconds;
140
+ /** Structural marker for {@link isPluginStorageUploadError}. */
141
+ isPluginStorageUploadError = true;
142
+ constructor(message, reason, options = {}) {
143
+ super(message, options.code ?? codeForReason(reason, options.status));
144
+ this.name = "PluginStorageUploadError";
145
+ this.reason = reason;
146
+ this.status = options.status;
147
+ if (options.retryAfterSeconds !== void 0) {
148
+ this.retryAfterSeconds = options.retryAfterSeconds;
149
+ }
150
+ }
151
+ };
152
+ function parsePluginStorageRetryAfter(raw, nowMs = Date.now()) {
153
+ if (typeof raw === "number") {
154
+ return Number.isFinite(raw) && raw >= 0 ? Math.ceil(raw) : void 0;
155
+ }
156
+ if (typeof raw !== "string" || raw.trim().length === 0) {
157
+ return void 0;
158
+ }
159
+ const text = raw.trim();
160
+ if (/^\d+$/.test(text)) {
161
+ return Number(text);
162
+ }
163
+ const at = /[A-Za-z]/.test(text) ? Date.parse(text) : Number.NaN;
164
+ return Number.isNaN(at) ? void 0 : Math.max(0, Math.ceil((at - nowMs) / 1e3));
165
+ }
166
+ function validatePluginStoragePrefix(raw) {
167
+ if (typeof raw !== "string" || raw.trim().length === 0) {
168
+ return { ok: false, error: "A storage path prefix is required." };
169
+ }
170
+ if (raw.length > PLUGIN_STORAGE_PREFIX_MAX_LENGTH) {
171
+ return {
172
+ ok: false,
173
+ error: `The storage path prefix must be at most ${PLUGIN_STORAGE_PREFIX_MAX_LENGTH} characters, so the stored path fits ${PLUGIN_STORAGE_PATH_MAX_LENGTH}.`
174
+ };
175
+ }
176
+ if (!/^[A-Za-z0-9_\-/]+$/.test(raw)) {
177
+ return {
178
+ ok: false,
179
+ error: "The storage path prefix may contain only letters, digits, '-', '_' and '/' separators."
180
+ };
181
+ }
182
+ if (raw.split("/").some((segment) => segment.length === 0)) {
183
+ return {
184
+ ok: false,
185
+ error: "The storage path prefix must not start or end with '/' or contain an empty segment."
186
+ };
187
+ }
188
+ return { ok: true, prefix: raw };
189
+ }
190
+ var PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES = Object.freeze({
191
+ pdf: ["application/pdf"],
192
+ png: ["image/png"],
193
+ jpg: ["image/jpeg", "image/pjpeg", "image/jpg"],
194
+ jpeg: ["image/jpeg", "image/pjpeg", "image/jpg"],
195
+ gif: ["image/gif"],
196
+ webp: ["image/webp"],
197
+ doc: ["application/msword"],
198
+ docx: ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"],
199
+ xls: ["application/vnd.ms-excel"],
200
+ xlsx: ["application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"],
201
+ ppt: ["application/vnd.ms-powerpoint"],
202
+ pptx: ["application/vnd.openxmlformats-officedocument.presentationml.presentation"],
203
+ odt: ["application/vnd.oasis.opendocument.text"],
204
+ ods: ["application/vnd.oasis.opendocument.spreadsheet"],
205
+ odp: ["application/vnd.oasis.opendocument.presentation"],
206
+ txt: ["text/plain"],
207
+ csv: ["text/csv", "application/vnd.ms-excel", "application/csv", "text/plain"],
208
+ zip: ["application/zip", "application/x-zip-compressed", "application/x-zip"],
209
+ mp3: ["audio/mpeg", "audio/mp3"],
210
+ m4a: ["audio/mp4", "audio/x-m4a"],
211
+ mp4: ["video/mp4"]
212
+ });
213
+ var OCTET_STREAM = "application/octet-stream";
214
+ function pluginStorageSafeExtension(fileName) {
215
+ if (typeof fileName !== "string" || fileName.trim().length === 0) {
216
+ return "";
217
+ }
218
+ const leaf = fileName.split(/[\\/]/).pop() ?? "";
219
+ const dot = leaf.lastIndexOf(".");
220
+ if (dot < 0) {
221
+ return "";
222
+ }
223
+ const body = leaf.slice(dot + 1);
224
+ return /^[A-Za-z0-9]{1,10}$/.test(body) ? `.${body.toLowerCase()}` : "";
225
+ }
226
+ function resolvePluginStorageContentType(fileName, declaredContentType) {
227
+ const extension = pluginStorageSafeExtension(fileName).slice(1);
228
+ const allowed = extension.length > 0 && Object.prototype.hasOwnProperty.call(PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES, extension) ? PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES[extension] : void 0;
229
+ if (allowed === void 0) {
230
+ return {
231
+ ok: false,
232
+ error: "This file type is not allowed. Allowed: " + Object.keys(PLUGIN_STORAGE_UPLOAD_CONTENT_TYPES).sort().join(", ") + "."
233
+ };
234
+ }
235
+ const declared = mediaTypeOf(declaredContentType);
236
+ if (declared === void 0 || declared === OCTET_STREAM || allowed.includes(declared)) {
237
+ return { ok: true, contentType: allowed[0] };
238
+ }
239
+ return {
240
+ ok: false,
241
+ error: `The declared content type '${declared}' does not match the file extension '.${extension}'.`
242
+ };
243
+ }
244
+ function mediaTypeOf(raw) {
245
+ if (typeof raw !== "string") {
246
+ return void 0;
247
+ }
248
+ const mediaType = raw.split(";")[0].trim().toLowerCase();
249
+ return /^[a-z0-9!#$&^_.+-]+\/[a-z0-9!#$&^_.+-]+$/.test(mediaType) ? mediaType : void 0;
250
+ }
251
+ function codeForReason(reason, status) {
252
+ if (status !== void 0) {
253
+ return mcpErrorCodeFromHttpStatus(status);
254
+ }
255
+ switch (reason) {
256
+ case "invalid_prefix":
257
+ case "too_large":
258
+ case "unsupported_type":
259
+ return "invalid_request";
260
+ case "timeout":
261
+ return "timeout";
262
+ case "unauthorized":
263
+ return "unauthorized";
264
+ case "forbidden":
265
+ return "forbidden";
266
+ case "busy":
267
+ return "unavailable";
268
+ case "rate_limited":
269
+ return "rate_limited";
270
+ case "aborted":
271
+ return "timeout";
272
+ default:
273
+ return "internal";
274
+ }
275
+ }
276
+
103
277
  // src/host/version-skew.ts
104
278
  var EXTENSION_VERSION_HEADER = "x-extension-version";
105
279
  var EXTENSION_ACTIVE_VERSION_HEADER = "x-extension-active-version";
@@ -413,6 +587,9 @@ var WorkerRemoteDomTransport = class {
413
587
  case "ethisys:mcp:uploadDocument":
414
588
  this.dispatchMcp(message, "ethisys:mcp:uploadDocument:result", (m) => this.handleUploadDocument(m));
415
589
  return;
590
+ case "ethisys:mcp:uploadToStorage":
591
+ this.dispatchMcp(message, "ethisys:mcp:uploadToStorage:result", (m) => this.handleUploadToStorage(m));
592
+ return;
416
593
  case "ethisys:mcp:abort":
417
594
  this.inFlightAborts.get(message.id ?? "")?.abort();
418
595
  return;
@@ -650,11 +827,76 @@ var WorkerRemoteDomTransport = class {
650
827
  this.replyError(message.id, "ethisys:mcp:uploadDocument:result", err);
651
828
  }
652
829
  }
653
- replyError(id, type, err) {
830
+ async handleUploadToStorage(message) {
831
+ const resultType = "ethisys:mcp:uploadToStorage:result";
832
+ let token;
833
+ try {
834
+ token = await this.capabilityTokenProvider();
835
+ } catch (err) {
836
+ this.replyError(message.id, resultType, err, true);
837
+ return;
838
+ }
839
+ if (this.disposed) {
840
+ return;
841
+ }
842
+ try {
843
+ const result = await this.mcpClient.fetch({
844
+ kind: "uploadToStorage",
845
+ meta: message.meta,
846
+ buffer: message.buffer,
847
+ capabilityToken: token,
848
+ signal: this.requestSignal(message.id)
849
+ });
850
+ this.reportVersionSkew(result);
851
+ this.safePostMessage({
852
+ id: message.id,
853
+ type: resultType,
854
+ ok: result.ok,
855
+ data: result.data,
856
+ error: result.error,
857
+ code: classifyHostResponse(result),
858
+ // The HTTP status, on failure only. A bare number carries no host internals, and it
859
+ // is the only way the plugin can tell 413 (file too large) from 507 (quota) from 503
860
+ // (storage lock busy): the closed MCP code folds all three into `internal`/`unavailable`.
861
+ ...!result.ok && typeof result.status === "number" ? { status: result.status } : {},
862
+ // The server's `Retry-After` (429), as whole seconds, so a plugin can show how long
863
+ // to wait. Never acted on here: an upload is not retried automatically.
864
+ ...retryAfterField(result)
865
+ });
866
+ } catch (err) {
867
+ this.replyError(message.id, resultType, err, true);
868
+ }
869
+ }
870
+ replyError(id, type, err, includeStatus = false) {
654
871
  const message = err instanceof Error ? err.message : "MCP request failed";
655
- this.safePostMessage({ id, type, ok: false, error: message, code: classifyHostError(err) });
872
+ const status = includeStatus ? statusOf(err) : void 0;
873
+ const retryAfterSeconds = includeStatus && err !== null && typeof err === "object" ? parsePluginStorageRetryAfter(err.retryAfterSeconds) : void 0;
874
+ this.safePostMessage({
875
+ id,
876
+ type,
877
+ ok: false,
878
+ error: message,
879
+ code: classifyHostError(err),
880
+ ...status !== void 0 ? { status } : {},
881
+ ...retryAfterSeconds !== void 0 ? { retryAfterSeconds } : {}
882
+ });
656
883
  }
657
884
  };
885
+ function retryAfterField(result) {
886
+ if (result.ok || !result.headers) {
887
+ return {};
888
+ }
889
+ const key = Object.keys(result.headers).find((k) => k.toLowerCase() === "retry-after");
890
+ const seconds = key === void 0 ? void 0 : parsePluginStorageRetryAfter(result.headers[key]);
891
+ return seconds === void 0 ? {} : { retryAfterSeconds: seconds };
892
+ }
893
+ function statusOf(err) {
894
+ if (err === null || typeof err !== "object") {
895
+ return void 0;
896
+ }
897
+ const candidate = err;
898
+ return typeof candidate.status === "number" ? candidate.status : typeof candidate.statusCode === "number" ? candidate.statusCode : void 0;
899
+ }
658
900
 
659
901
  // src/host/worker/component-registry.ts
660
902
  var CONTRACT_B_PRIMITIVES = [
@@ -678,6 +920,83 @@ var CONTRACT_B_PRIMITIVES = [
678
920
  ];
679
921
  new Set(CONTRACT_B_PRIMITIVES);
680
922
 
923
+ // src/mock-host/MockPluginStorage.ts
924
+ var MockPluginStorage = class {
925
+ files = /* @__PURE__ */ new Map();
926
+ rateLimit;
927
+ now;
928
+ windowStartMs = 0;
929
+ windowCount = 0;
930
+ constructor(options = {}) {
931
+ this.rateLimit = options.rateLimit;
932
+ this.now = options.now ?? Date.now;
933
+ }
934
+ /** Store `buffer` under a new path below `meta.pathPrefix`. */
935
+ store(meta, buffer) {
936
+ this.enforceRateLimit();
937
+ const prefix = validatePluginStoragePrefix(meta.pathPrefix);
938
+ if (!prefix.ok) {
939
+ throw new PluginStorageUploadError(prefix.error, "invalid_prefix", { status: 400 });
940
+ }
941
+ if (buffer.byteLength > PLUGIN_STORAGE_UPLOAD_MAX_BYTES) {
942
+ throw new PluginStorageUploadError(
943
+ `The upload exceeds the ${PLUGIN_STORAGE_UPLOAD_MAX_BYTES}-byte limit.`,
944
+ "too_large",
945
+ { status: 413 }
946
+ );
947
+ }
948
+ const fileName = meta.fileName && meta.fileName.length > 0 ? meta.fileName : "upload.bin";
949
+ const type = resolvePluginStorageContentType(fileName, meta.contentType);
950
+ if (!type.ok) {
951
+ throw new PluginStorageUploadError(type.error, "unsupported_type", { status: 415 });
952
+ }
953
+ const path2 = `${prefix.prefix}/${randomGuidN()}${pluginStorageSafeExtension(fileName)}`;
954
+ const bytes = new Uint8Array(buffer.slice(0));
955
+ this.files.set(path2, { path: path2, fileName, contentType: type.contentType, bytes });
956
+ return { path: path2, fileName, contentType: type.contentType, sizeBytes: bytes.byteLength };
957
+ }
958
+ /** The kernel refuses before the handler runs, so this is checked before anything else. */
959
+ enforceRateLimit() {
960
+ if (this.rateLimit === void 0) {
961
+ return;
962
+ }
963
+ const windowMs = (this.rateLimit.windowSeconds ?? 60) * 1e3;
964
+ const now = this.now();
965
+ if (this.windowCount === 0 || now - this.windowStartMs >= windowMs) {
966
+ this.windowStartMs = now;
967
+ this.windowCount = 0;
968
+ }
969
+ this.windowCount += 1;
970
+ if (this.windowCount > this.rateLimit.maxRequests) {
971
+ const retryAfterSeconds = Math.max(1, Math.ceil((this.windowStartMs + windowMs - now) / 1e3));
972
+ throw new PluginStorageUploadError(
973
+ "Rate limit exceeded. Please try again later.",
974
+ "rate_limited",
975
+ { status: 429, retryAfterSeconds }
976
+ );
977
+ }
978
+ }
979
+ /** The stored file at `path`, or `undefined`. */
980
+ get(path2) {
981
+ return this.files.get(path2);
982
+ }
983
+ /** Every stored file, in upload order. */
984
+ list() {
985
+ return [...this.files.values()];
986
+ }
987
+ /** Forget every stored file. */
988
+ clear() {
989
+ this.files.clear();
990
+ }
991
+ };
992
+ function randomGuidN() {
993
+ const bytes = new Uint8Array(16);
994
+ globalThis.crypto.getRandomValues(bytes);
995
+ bytes[6] = bytes[6] & 15 | 64;
996
+ bytes[8] = bytes[8] & 63 | 128;
997
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
998
+ }
999
+
681
1000
  // src/mock-host/cli.ts
682
1001
  async function loadResources(dir) {
683
1002
  const absDir = path.resolve(dir);
@@ -923,6 +1242,7 @@ import {
923
1242
  WorkerRemoteDomTransport,
924
1243
  SemanticComponentRegistry,
925
1244
  } from "@ethisyscore/extension-runtime/host";
1245
+ import { MockPluginStorage } from "@ethisyscore/extension-runtime/mock-host";
926
1246
 
927
1247
  const PRIMITIVES = ${safePrimitives};
928
1248
  const bundleUrl = ${safeUrl};
@@ -938,12 +1258,18 @@ const registry = SemanticComponentRegistry.fromMap(componentMap);
938
1258
 
939
1259
  // Mock-host MCP client: no platform, so every tool/resource call resolves
940
1260
  // to a friendly placeholder. Authors who need real MCP traffic should run
941
- // against the full platform host.
1261
+ // against the full platform host. uploadToStorage writes into an in-memory
1262
+ // plugin storage and returns a server-style {prefix}/{guid}{ext} path; an
1263
+ // invalid prefix or an over-size body throws with status 400 / 413, which the
1264
+ // transport forwards to the plugin as a typed error.
1265
+ const pluginStorage = new MockPluginStorage();
942
1266
  const mcpClient = {
943
- fetch: async (req) => ({
944
- ok: true,
945
- data: { mockHost: true, kind: req.kind, name: req.name ?? req.uri },
946
- }),
1267
+ fetch: async (req) => req.kind === "uploadToStorage"
1268
+ ? { ok: true, data: pluginStorage.store(req.meta, req.buffer) }
1269
+ : {
1270
+ ok: true,
1271
+ data: { mockHost: true, kind: req.kind, name: req.name ?? req.uri },
1272
+ },
947
1273
  };
948
1274
 
949
1275
  // The capability token never crosses the worker boundary \u2014 supply a marker
@@ -966,10 +1292,15 @@ transport.onRemoteDom(() => {
966
1292
  // devtools (e.g., to terminate the worker between iteration cycles).
967
1293
  window.__MOCK_HOST_TRANSPORT__ = transport;
968
1294
  window.__MOCK_HOST_REGISTRY__ = registry;
1295
+ window.__MOCK_HOST_PLUGIN_STORAGE__ = pluginStorage;
969
1296
  `;
970
1297
  }
1298
+ var defaultMockPluginStorage = new MockPluginStorage();
971
1299
  var defaultMockMcpClient = {
972
1300
  fetch: async (req) => {
1301
+ if (req.kind === "uploadToStorage") {
1302
+ return { ok: true, data: defaultMockPluginStorage.store(req.meta, req.buffer) };
1303
+ }
973
1304
  if (req.kind === "uploadDocument") {
974
1305
  return {
975
1306
  ok: true,
@@ -1151,6 +1482,6 @@ async function run(args) {
1151
1482
  return { url, close: () => server.close() };
1152
1483
  }
1153
1484
 
1154
- export { bootContractBHost, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
1485
+ export { bootContractBHost, defaultMockMcpClient, defaultMockPluginStorage, loadResources, loadTools, normaliseEntry, parseArgs, renderEntryModule, renderIndexHtml, renderWorkerBundleEntryModule, renderWorkerBundleIndexHtml, run };
1155
1486
  //# sourceMappingURL=cli.js.map
1156
1487
  //# sourceMappingURL=cli.js.map