PageSourceSearch

https://othoba.com/.webmcp/bridge.js

js othoba.com collected 2026-09-24 10:27:52 UTC 47,616 bytes, 1,489 lines download raw bytes

1// src/tool-pack.ts
2function isDynamicPack(pack) {
3  return pack.kind === "dynamic";
4}
5function ok(data) {
6  return {
7    content: [{ type: "text", text: JSON.stringify(data) }],
8    structuredContent: data
9  };
10}
11function customResult(result) {
12  return result;
13}
14function errorResult(message) {
15  return {
16    content: [{ type: "text", text: message }],
17    isError: true
18  };
19}
20
21// src/bridge/registry.ts
22function parsePackList(raw) {
23  if (raw === void 0) return null;
24  return raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
25}
26var DEFAULT_MCP_URL = "/mcp";
27function normalizeMcpUrl(raw) {
28  const v = (raw ?? "").trim();
29  if (v.toLowerCase() === "none") return void 0;
30  if (v === "") return DEFAULT_MCP_URL;
31  return v;
32}
33function selectActivePacks(all, requested, defaults) {
34  const names = requested ?? defaults;
35  const byName = new Map(all.map((p) => [p.name, p]));
36  const active = [];
37  for (const name of names) {
38    const pack = byName.get(name);
39    if (pack) active.push(pack);
40    else
41      console.warn(
42        `[webmcp-interceptor] Requested pack "${name}" is not bundled; skipping.`
43      );
44  }
45  return active;
46}
47function resolveStaticPacks(packs) {
48  const resolved = [];
49  for (const pack of packs) {
50    if (!isDynamicPack(pack)) {
51      resolved.push({
52        name: pack.name,
53        tools: pack.tools,
54        handlers: pack.handlers,
55        dynamic: false
56      });
57    }
58  }
59  return resolved;
60}
61async function resolveDynamicPacks(packs, ctx) {
62  const dynamics = packs.filter(isDynamicPack);
63  return Promise.all(
64    dynamics.map(async (pack) => {
65      try {
66        const { tools, handlers } = await pack.resolve(ctx);
67        return { name: pack.name, tools, handlers, dynamic: true };
68      } catch (err) {
69        console.warn(
70          `[webmcp-interceptor] Dynamic pack "${pack.name}" failed to resolve; skipping.`,
71          err
72        );
73        return { name: pack.name, tools: [], handlers: {}, dynamic: true };
74      }
75    })
76  );
77}
78function buildRegistry(packs) {
79  const tools = [];
80  const handlers = {};
81  const seen = /* @__PURE__ */ new Map();
82  for (const pack of packs) {
83    for (const tool of pack.tools) {
84      if (!pack.handlers[tool.name]) {
85        throw new Error(
86          `Tool-pack "${pack.name}" declares tool "${tool.name}" but has no matching handler.`
87        );
88      }
89    }
90    for (const name of Object.keys(pack.handlers)) {
91      if (!pack.tools.some((t) => t.name === name)) {
92        throw new Error(
93          `Tool-pack "${pack.name}" has a handler for "${name}" with no matching tool descriptor.`
94        );
95      }
96    }
97    for (const tool of pack.tools) {
98      const existing = seen.get(tool.name);
99      if (existing) {
100        if (pack.dynamic) {
101          console.warn(
102            `[webmcp-interceptor] Tool "${tool.name}" from dynamic pack "${pack.name}" collides with "${existing}"; skipping the dynamic one.`
103          );
104          continue;
105        }
106        throw new Error(
107          `Tool name collision: "${tool.name}" registered by both "${existing}" and "${pack.name}".`
108        );
109      }
110      const handler = pack.handlers[tool.name];
111      if (!handler) {
112        throw new Error(
113          `Tool-pack "${pack.name}" declares tool "${tool.name}" but has no matching handler.`
114        );
115      }
116      seen.set(tool.name, pack.name);
117      tools.push(tool);
118      handlers[tool.name] = handler;
119    }
120  }
121  return { tools, handlers };
122}
123
124// src/bridge/packs/c2pa/cbor.ts
125var BREAK = 255;
126var textDecoder = new TextDecoder("utf-8", { fatal: false });
127function decodeCbor(bytes) {
128  return new CborDecoder(bytes).decode();
129}
130function isCborTag(v) {
131  return typeof v === "object" && v !== null && !(v instanceof Uint8Array) && !Array.isArray(v) && !(v instanceof Map) && "tag" in v;
132}
133function isCborSimple(v) {
134  return typeof v === "object" && v !== null && !(v instanceof Uint8Array) && !Array.isArray(v) && !(v instanceof Map) && "simple" in v;
135}
136var CborDecoder = class {
137  constructor(bytes) {
138    this.bytes = bytes;
139    this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
140  }
141  view;
142  off = 0;
143  decode() {
144    return this.readValue(0);
145  }
146  /** Read one item. `depth` guards against pathological nesting. */
147  readValue(depth) {
148    if (depth > 256) throw new Error("CBOR nesting too deep");
149    const ib = this.u8();
150    const major = ib >> 5;
151    const ai = ib & 31;
152    switch (major) {
153      case 0:
154        return this.readUint(ai);
155      case 1: {
156        const n = this.readUint(ai);
157        return typeof n === "bigint" ? -1n - n : -1 - n;
158      }
159      case 2:
160        return this.readByteString(ai, depth);
161      case 3:
162        return textDecoder.decode(this.readByteString(ai, depth));
163      case 4:
164        return this.readArray(ai, depth);
165      case 5:
166        return this.readMap(ai, depth);
167      case 6: {
168        const tag = this.asLength(this.readUint(ai));
169        return { tag, value: this.readValue(depth + 1) };
170      }
171      default:
172        return this.readSimple(ai);
173    }
174  }
175  /** Read the argument that follows the initial byte (major 0/1/6). */
176  readUint(ai) {
177    if (ai < 24) return ai;
178    if (ai === 24) return this.u8();
179    if (ai === 25) {
180      const v = this.view.getUint16(this.off);
181      this.off += 2;
182      return v;
183    }
184    if (ai === 26) {
185      const v = this.view.getUint32(this.off);
186      this.off += 4;
187      return v;
188    }
189    if (ai === 27) {
190      const v = this.view.getBigUint64(this.off);
191      this.off += 8;
192      return v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v) : v;
193    }
194    throw new Error(`Invalid CBOR additional-info value: ${ai}`);
195  }
196  readByteString(ai, depth) {
197    if (ai === 31) {
198      const chunks = [];
199      let total = 0;
200      for (; ; ) {
201        if (this.peek() === BREAK) {
202          this.off += 1;
203          break;
204        }
205        const chunk = this.readValue(depth + 1);
206        if (!(chunk instanceof Uint8Array)) {
207          throw new Error("Invalid indefinite-length byte string chunk");
208        }
209        chunks.push(chunk);
210        total += chunk.length;
211      }
212      const out2 = new Uint8Array(total);
213      let p = 0;
214      for (const c of chunks) {
215        out2.set(c, p);
216        p += c.length;
217      }
218      return out2;
219    }
220    const len = this.asLength(this.readUint(ai));
221    const end = this.off + len;
222    if (end > this.bytes.length) throw new Error("CBOR byte string truncated");
223    const out = this.bytes.subarray(this.off, end);
224    this.off = end;
225    return out;
226  }
227  readArray(ai, depth) {
228    const out = [];
229    if (ai === 31) {
230      while (this.peek() !== BREAK) out.push(this.readValue(depth + 1));
231      this.off += 1;
232      return out;
233    }
234    const len = this.asLength(this.readUint(ai));
235    for (let i = 0; i < len; i++) out.push(this.readValue(depth + 1));
236    return out;
237  }
238  readMap(ai, depth) {
239    const out = /* @__PURE__ */ new Map();
240    if (ai === 31) {
241      while (this.peek() !== BREAK) {
242        const k = this.readValue(depth + 1);
243        out.set(k, this.readValue(depth + 1));
244      }
245      this.off += 1;
246      return out;
247    }
248    const len = this.asLength(this.readUint(ai));
249    for (let i = 0; i < len; i++) {
250      const k = this.readValue(depth + 1);
251      out.set(k, this.readValue(depth + 1));
252    }
253    return out;
254  }
255  readSimple(ai) {
256    switch (ai) {
257      case 20:
258        return false;
259      case 21:
260        return true;
261      case 22:
262        return null;
263      case 23:
264        return void 0;
265      case 24:
266        return { simple: this.u8() };
267      // one-byte simple value (32-255)
268      case 25:
269        return this.readHalfFloat();
270      case 26: {
271        const v = this.view.getFloat32(this.off);
272        this.off += 4;
273        return v;
274      }
275      case 27: {
276        const v = this.view.getFloat64(this.off);
277        this.off += 8;
278        return v;
279      }
280      default:
281        return { simple: ai };
282    }
283  }
284  /** IEEE 754 half-precision (binary16) → JS number. */
285  readHalfFloat() {
286    const half = this.view.getUint16(this.off);
287    this.off += 2;
288    const exp = (half & 31744) >> 10;
289    const frac = half & 1023;
290    const sign = half & 32768 ? -1 : 1;
291    if (exp === 0) return sign * 2 ** -14 * (frac / 1024);
292    if (exp === 31) return frac ? NaN : sign * Infinity;
293    return sign * 2 ** (exp - 15) * (1 + frac / 1024);
294  }
295  u8() {
296    if (this.off >= this.bytes.length) throw new Error("CBOR truncated");
297    const v = this.view.getUint8(this.off);
298    this.off += 1;
299    return v;
300  }
301  peek() {
302    if (this.off >= this.bytes.length) throw new Error("CBOR truncated");
303    return this.view.getUint8(this.off);
304  }
305  /** Coerce a length/tag argument to a sane JS number or throw. */
306  asLength(v) {
307    if (typeof v === "bigint" || v > 2147483647) {
308      throw new Error("CBOR length/tag exceeds supported range");
309    }
310    return v;
311  }
312};
313
314// src/bridge/packs/c2pa/x509.ts
315var textDecoder2 = new TextDecoder("utf-8", { fatal: false });
316var OID_COMMON_NAME = [85, 4, 3];
317function parseCertificate(der) {
318  try {
319    return parseCertificateUnsafe(der);
320  } catch {
321    return {};
322  }
323}
324function parseCertificateUnsafe(der) {
325  const view = new DataView(der.buffer, der.byteOffset, der.byteLength);
326  const cert = readTlv(view, 0, der.length);
327  if (!cert) return {};
328  const tbs = readTlv(view, cert.contentStart, cert.contentEnd);
329  if (!tbs) return {};
330  const fields = readChildren(view, der, tbs.contentStart, tbs.contentEnd);
331  let i = 0;
332  if (fields[i] && fields[i]?.tag === 160) i += 1;
333  const issuer = fields[i + 2];
334  const validity = fields[i + 3];
335  const subject = fields[i + 4];
336  const info = {};
337  if (issuer) info.issuerCN = extractCommonName(view, der, issuer);
338  if (subject) info.subjectCN = extractCommonName(view, der, subject);
339  if (validity) {
340    const times = readChildren(view, der, validity.contentStart, validity.contentEnd);
341    if (times[0]) info.notBefore = parseTime(der, times[0]);
342    if (times[1]) info.notAfter = parseTime(der, times[1]);
343  }
344  return info;
345}
346function readTlv(view, off, limit) {
347  if (off + 2 > limit) return null;
348  const tag = view.getUint8(off);
349  let p = off + 1;
350  let len = view.getUint8(p);
351  p += 1;
352  if (len & 128) {
353    const n = len & 127;
354    if (n === 0 || n > 4) return null;
355    len = 0;
356    for (let i = 0; i < n; i++) {
357      len = len << 8 | view.getUint8(p);
358      p += 1;
359    }
360  }
361  const contentStart = p;
362  const contentEnd = p + len;
363  if (contentEnd > limit) return null;
364  return { tag, contentStart, contentEnd, end: contentEnd };
365}
366function readChildren(view, bytes, start, end) {
367  const out = [];
368  let off = start;
369  while (off < end) {
370    const node = readTlv(view, off, end);
371    if (!node) break;
372    out.push(node);
373    off = node.end;
374  }
375  return out;
376}
377function extractCommonName(view, bytes, name) {
378  for (const rdn of readChildren(view, bytes, name.contentStart, name.contentEnd)) {
379    for (const atv of readChildren(view, bytes, rdn.contentStart, rdn.contentEnd)) {
380      const parts = readChildren(view, bytes, atv.contentStart, atv.contentEnd);
381      const oid = parts[0];
382      const value = parts[1];
383      if (oid && value && oid.tag === 6 && oidEquals(bytes, oid, OID_COMMON_NAME)) {
384        return textDecoder2.decode(bytes.subarray(value.contentStart, value.contentEnd));
385      }
386    }
387  }
388  return void 0;
389}
390function oidEquals(bytes, node, expected) {
391  const len = node.contentEnd - node.contentStart;
392  if (len !== expected.length) return false;
393  for (let i = 0; i < len; i++) {
394    if (bytes[node.contentStart + i] !== expected[i]) return false;
395  }
396  return true;
397}
398function parseTime(bytes, node) {
399  const raw = textDecoder2.decode(bytes.subarray(node.contentStart, node.contentEnd));
400  const isUtc = node.tag === 23;
401  const m = isUtc ? /^(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})?Z?$/.exec(raw) : /^(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})?Z?$/.exec(raw);
402  if (!m) return raw;
403  let year = Number(m[1]);
404  if (isUtc) year += year < 50 ? 2e3 : 1900;
405  const iso = `${pad(year, 4)}-${m[2]}-${m[3]}T${m[4]}:${m[5]}:${m[6] ?? "00"}Z`;
406  return iso;
407}
408function pad(n, width) {
409  return String(n).padStart(width, "0");
410}
411
412// src/bridge/packs/c2pa/manifest.ts
413function buildManifestStore(store) {
414  const manifests = store.children.map(decodeManifest);
415  const active = manifests[manifests.length - 1];
416  const result = {
417    manifestCount: manifests.length,
418    manifests
419  };
420  if (store.label !== void 0) result.label = store.label;
421  if (active?.label !== void 0) result.activeManifestLabel = active.label;
422  return result;
423}
424function decodeManifest(node) {
425  let claimNode;
426  let signatureNode;
427  let assertionStore;
428  for (const child of node.children) {
429    const label = child.label ?? "";
430    if (label.startsWith("c2pa.claim") || child.contentType === "c2cl") {
431      claimNode = child;
432    } else if (label === "c2pa.signature" || child.contentType === "c2cs") {
433      signatureNode = child;
434    } else if (label === "c2pa.assertions" || child.contentType === "c2as") {
435      assertionStore = child;
436    }
437  }
438  const assertions = assertionStore ? assertionStore.children.map(decodeAssertion) : [];
439  const manifest = { assertions };
440  if (node.label !== void 0) manifest.label = node.label;
441  if (claimNode?.cbor !== void 0) {
442    const claim = cborToJson(claimNode.cbor);
443    if (claim !== null && typeof claim === "object" && !Array.isArray(claim)) {
444      manifest.claim = claim;
445    }
446  }
447  if (signatureNode?.cbor !== void 0) {
448    manifest.signature = decodeCoseSignature(signatureNode.cbor);
449  }
450  return manifest;
451}
452function decodeAssertion(node) {
453  const out = {};
454  if (node.label !== void 0) out.label = node.label;
455  if (node.cbor !== void 0) {
456    out.data = cborToJson(node.cbor);
457  } else if (node.json !== void 0) {
458    out.data = node.json;
459  } else if (node.binary !== void 0) {
460    out.binary = node.binary;
461  }
462  return out;
463}
464var COSE_ALG = {
465  [-7]: "ES256",
466  [-35]: "ES384",
467  [-36]: "ES512",
468  [-8]: "EdDSA",
469  [-37]: "PS256",
470  [-38]: "PS384",
471  [-39]: "PS512",
472  [-257]: "RS256",
473  [-258]: "RS384",
474  [-259]: "RS512"
475};
476function decodeCoseSignature(cose) {
477  const arr = isCborTag(cose) ? cose.value : cose;
478  if (!Array.isArray(arr) || arr.length < 4) return {};
479  const protectedHeader = decodeProtectedHeader(arr[0]);
480  const unprotected = arr[1] instanceof Map ? arr[1] : /* @__PURE__ */ new Map();
481  const sig = {};
482  const algId = asInt(protectedHeader.get(1) ?? unprotected.get(1));
483  if (algId !== void 0) sig.alg = COSE_ALG[algId] ?? `COSE(${algId})`;
484  const certs = readX5Chain(protectedHeader.get(33) ?? unprotected.get(33));
485  if (certs.length > 0) {
486    sig.certChainLength = certs.length;
487    const first = certs[0];
488    if (first) {
489      const info = parseCertificate(first);
490      if (info.subjectCN !== void 0) sig.signedBy = info.subjectCN;
491      if (info.issuerCN !== void 0) sig.certifiedBy = info.issuerCN;
492      if (info.notBefore !== void 0) sig.certValidFrom = info.notBefore;
493      if (info.notAfter !== void 0) sig.certValidTo = info.notAfter;
494    }
495  }
496  return sig;
497}
498function decodeProtectedHeader(value) {
499  if (value instanceof Uint8Array && value.length > 0) {
500    const decoded = decodeCbor(value);
501    if (decoded instanceof Map) return decoded;
502  }
503  return /* @__PURE__ */ new Map();
504}
505function readX5Chain(value) {
506  if (value instanceof Uint8Array) return [value];
507  if (Array.isArray(value)) {
508    return value.filter((c) => c instanceof Uint8Array);
509  }
510  return [];
511}
512function asInt(value) {
513  if (typeof value === "number" && Number.isInteger(value)) return value;
514  if (typeof value === "bigint") return Number(value);
515  return void 0;
516}
517var MAX_INLINE_BYTES = 64;
518function cborToJson(value) {
519  if (value === null || value === void 0) return value ?? null;
520  if (typeof value === "bigint") return value.toString();
521  if (value instanceof Uint8Array) return bytesToJson(value);
522  if (Array.isArray(value)) return value.map(cborToJson);
523  if (value instanceof Map) {
524    const obj = {};
525    for (const [k, v] of value) obj[keyToString(k)] = cborToJson(v);
526    return obj;
527  }
528  if (isCborTag(value)) {
529    return { $tag: value.tag, value: cborToJson(value.value) };
530  }
531  if (isCborSimple(value)) {
532    return { $simple: value.simple };
533  }
534  return value;
535}
536function bytesToJson(b) {
537  if (b.length <= MAX_INLINE_BYTES) return { $bytes: b.length, hex: toHex(b) };
538  return { $bytes: b.length, hex: `${toHex(b.subarray(0, 32))}\u2026` };
539}
540function keyToString(k) {
541  if (typeof k === "string") return k;
542  if (typeof k === "number" || typeof k === "bigint" || typeof k === "boolean") {
543    return String(k);
544  }
545  return JSON.stringify(cborToJson(k));
546}
547function toHex(b) {
548  let s = "";
549  for (let i = 0; i < b.length; i++) {
550    const byte = b[i];
551    if (byte === void 0) break;
552    s += byte.toString(16).padStart(2, "0");
553  }
554  return s;
555}
556
557// src/bridge/packs/c2pa/jumbf.ts
558var textDecoder3 = new TextDecoder("utf-8", { fatal: false });
559function detectImageFormat(bytes) {
560  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) {
561    return "jpeg";
562  }
563  if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) {
564    return "png";
565  }
566  return "unknown";
567}
568function extractC2paJumbf(bytes) {
569  switch (detectImageFormat(bytes)) {
570    case "jpeg":
571      return extractFromJpeg(bytes);
572    case "png":
573      return extractFromPng(bytes);
574    default:
575      return null;
576  }
577}
578function parseManifestStore(jumbf) {
579  const view = new DataView(jumbf.buffer, jumbf.byteOffset, jumbf.byteLength);
580  const top = readBox(view, jumbf, 0, jumbf.length);
581  if (!top || top.type !== "jumb") return null;
582  return parseSuperbox(view, jumbf, top.payloadStart, top.payloadEnd, 0);
583}
584function extractFromJpeg(bytes) {
585  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
586  if (view.getUint16(0) !== 65496) return null;
587  const packets = /* @__PURE__ */ new Map();
588  let off = 2;
589  while (off + 2 <= bytes.length) {
590    if (view.getUint8(off) !== 255) break;
591    let marker = view.getUint8(off + 1);
592    while (marker === 255 && off + 2 < bytes.length) {
593      off += 1;
594      marker = view.getUint8(off + 1);
595    }
596    off += 2;
597    if (marker === 217 || marker === 218) break;
598    if (marker === 1 || marker >= 208 && marker <= 215) continue;
599    if (off + 2 > bytes.length) break;
600    const segLen = view.getUint16(off);
601    const segStart = off + 2;
602    const segEnd = off + segLen;
603    if (segLen < 2 || segEnd > bytes.length) break;
604    if (marker === 235 && segEnd - segStart >= 8) {
605      if (view.getUint16(segStart) === 19024) {
606        const en = view.getUint16(segStart + 2);
607        const z = view.getUint32(segStart + 4);
608        const data = bytes.subarray(segStart + 8, segEnd);
609        const arr = packets.get(en) ?? [];
610        arr.push({ z, data });
611        packets.set(en, arr);
612      }
613    }
614    off = segEnd;
615  }
616  for (const arr of packets.values()) {
617    arr.sort((a, b) => a.z - b.z);
618    const first = arr[0];
619    if (!first) continue;
620    let headerLen = 8;
621    if (first.data.length >= 4) {
622      const fv = new DataView(
623        first.data.buffer,
624        first.data.byteOffset,
625        first.data.byteLength
626      );
627      if (fv.getUint32(0) === 1) headerLen = 16;
628    }
629    const pieces = [];
630    let total = 0;
631    for (let i = 0; i < arr.length; i++) {
632      const pk = arr[i];
633      if (!pk) continue;
634      const piece = i === 0 ? pk.data : pk.data.subarray(headerLen);
635      pieces.push(piece);
636      total += piece.length;
637    }
638    const buf = new Uint8Array(total);
639    let p = 0;
640    for (const piece of pieces) {
641      buf.set(piece, p);
642      p += piece.length;
643    }
644    if (isC2paStore(buf)) return buf;
645  }
646  return null;
647}
648function extractFromPng(bytes) {
649  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
650  let off = 8;
651  while (off + 8 <= bytes.length) {
652    const len = view.getUint32(off);
653    const type = ascii(bytes, off + 4, 4);
654    const dataStart = off + 8;
655    const dataEnd = dataStart + len;
656    if (dataEnd + 4 > bytes.length) break;
657    if (type === "caBX") {
658      const buf = bytes.subarray(dataStart, dataEnd);
659      return isC2paStore(buf) ? buf : null;
660    }
661    if (type === "IEND") break;
662    off = dataEnd + 4;
663  }
664  return null;
665}
666function readBox(view, bytes, offset, limit) {
667  if (offset + 8 > limit) return null;
668  let len = view.getUint32(offset);
669  const type = ascii(bytes, offset + 4, 4);
670  let headerLen = 8;
671  if (len === 1) {
672    if (offset + 16 > limit) return null;
673    const hi = view.getUint32(offset + 8);
674    const lo = view.getUint32(offset + 12);
675    len = hi * 4294967296 + lo;
676    headerLen = 16;
677  } else if (len === 0) {
678    len = limit - offset;
679  }
680  const boxEnd = offset + len;
681  if (len < headerLen || boxEnd > limit) return null;
682  return { type, payloadStart: offset + headerLen, payloadEnd: boxEnd, boxEnd };
683}
684function parseSuperbox(view, bytes, start, end, depth) {
685  const node = { contentType: "", children: [] };
686  if (depth > 64) return node;
687  let off = start;
688  const first = readBox(view, bytes, off, end);
689  if (first && first.type === "jumd") {
690    parseDescription(view, bytes, first.payloadStart, first.payloadEnd, node);
691    off = first.boxEnd;
692  }
693  while (off < end) {
694    const box = readBox(view, bytes, off, end);
695    if (!box) break;
696    switch (box.type) {
697      case "jumb":
698        node.children.push(
699          parseSuperbox(view, bytes, box.payloadStart, box.payloadEnd, depth + 1)
700        );
701        break;
702      case "cbor":
703        try {
704          node.cbor = decodeCbor(bytes.subarray(box.payloadStart, box.payloadEnd));
705        } catch {
706        }
707        break;
708      case "json":
709        try {
710          node.json = JSON.parse(
711            textDecoder3.decode(bytes.subarray(box.payloadStart, box.payloadEnd))
712          );
713        } catch {
714        }
715        break;
716      case "bidb":
717      // embedded-file data box
718      case "uuid":
719        node.binary = { size: box.payloadEnd - box.payloadStart };
720        break;
721      default:
722        break;
723    }
724    off = box.boxEnd;
725  }
726  return node;
727}
728function parseDescription(view, bytes, start, end, node) {
729  if (start + 17 > end) return;
730  node.contentType = ascii(bytes, start, 4);
731  const toggles = view.getUint8(start + 16);
732  let off = start + 17;
733  if (toggles & 2) {
734    let z = off;
735    while (z < end && view.getUint8(z) !== 0) z += 1;
736    node.label = textDecoder3.decode(bytes.subarray(off, z));
737  }
738}
739function isC2paStore(buf) {
740  if (buf.length < 16) return false;
741  const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
742  const top = readBox(view, buf, 0, buf.length);
743  if (!top || top.type !== "jumb") return false;
744  const desc = readBox(view, buf, top.payloadStart, top.payloadEnd);
745  if (!desc || desc.type !== "jumd") return false;
746  return ascii(buf, desc.payloadStart, 4) === "c2pa";
747}
748function ascii(bytes, off, len) {
749  let s = "";
750  for (let i = 0; i < len; i++) {
751    const c = bytes[off + i];
752    if (c === void 0) break;
753    s += String.fromCharCode(c);
754  }
755  return s;
756}
757
758// src/bridge/packs/c2pa/c2pa-executor.ts
759var MAX_SCAN_IMAGES = 100;
760var MAX_IMAGE_BYTES = 64 * 1024 * 1024;
761var FETCH_TIMEOUT_MS = 15e3;
762var SCAN_CONCURRENCY = 6;
763var SCAN_BUDGET_MS = 3e4;
764var NO_VERIFICATION_NOTE = "Manifest decoded only. Cryptographic signature, certificate-chain, and trust-list verification were NOT performed; treat provenance as unverified claims.";
765async function scanImages(limit, signal) {
766  const allSources = collectImageSources();
767  const capped = Math.min(limit, MAX_SCAN_IMAGES);
768  const sources = allSources.slice(0, capped);
769  const deadline = anySignal(signal, AbortSignal.timeout(SCAN_BUDGET_MS));
770  const results = await mapWithConcurrency(
771    sources,
772    SCAN_CONCURRENCY,
773    (src) => scanOne(src, deadline)
774  );
775  const withC2pa = results.filter((r) => r.hasC2pa).length;
776  return {
777    imageCount: allSources.length,
778    scanned: results.length,
779    withC2pa,
780    results
781  };
782}
783async function scanOne(src, signal) {
784  try {
785    const bytes = await fetchImageBytes(src, signal);
786    const analysis = analyzeBytes(bytes);
787    if (!analysis.store) {
788      return { src, hasC2pa: false, format: analysis.format };
789    }
790    const active = activeManifest(analysis.store);
791    const summary = {
792      src,
793      hasC2pa: true,
794      format: analysis.format,
795      manifestCount: analysis.store.manifestCount
796    };
797    const claimGenerator = readClaimGenerator(active?.claim);
798    const title = readTitle(active?.claim);
799    if (claimGenerator !== void 0) summary.claimGenerator = claimGenerator;
800    if (title !== void 0) summary.title = title;
801    if (active?.signature?.signedBy !== void 0) {
802      summary.signedBy = active.signature.signedBy;
803    }
804    return summary;
805  } catch (err) {
806    return { src, hasC2pa: false, error: errorMessage(err) };
807  }
808}
809async function inspectImage(opts, signal) {
810  const src = resolveSource(opts);
811  const bytes = await fetchImageBytes(src, signal);
812  const analysis = analyzeBytes(bytes);
813  if (!analysis.store) {
814    return {
815      src,
816      hasC2pa: false,
817      format: analysis.format,
818      message: analysis.format === "unknown" ? "Unsupported image format (only JPEG and PNG are parsed today)." : "No C2PA manifest found in this image."
819    };
820  }
821  return {
822    src,
823    hasC2pa: true,
824    format: analysis.format,
825    manifestStore: analysis.store,
826    validation: { signatureVerified: false, note: NO_VERIFICATION_NOTE }
827  };
828}
829function analyzeBytes(bytes) {
830  const format = detectImageFormat(bytes);
831  const jumbf = extractC2paJumbf(bytes);
832  if (!jumbf) return { format };
833  const tree = parseManifestStore(jumbf);
834  if (!tree) return { format };
835  return { format, store: buildManifestStore(tree) };
836}
837function collectImageSources() {
838  const seen = /* @__PURE__ */ new Set();
839  const out = [];
840  for (const img of Array.from(document.querySelectorAll("img"))) {
841    const el = img;
842    const src = el.currentSrc || el.src;
843    if (src && !seen.has(src)) {
844      seen.add(src);
845      out.push(src);
846    }
847  }
848  return out;
849}
850function resolveSource(opts) {
851  if (opts.url) {
852    return new URL(opts.url, document.location.href).toString();
853  }
854  if (opts.selector) {
855    const el = document.querySelector(opts.selector);
856    if (!el) throw new Error(`No element matches selector: ${opts.selector}`);
857    if (!(el instanceof HTMLImageElement)) {
858      throw new Error(`Element is not an <img>: ${opts.selector}`);
859    }
860    const src = el.currentSrc || el.src;
861    if (!src) throw new Error(`<img> has no resolved src: ${opts.selector}`);
862    return src;
863  }
864  throw new Error("Provide either a `selector` or a `url`.");
865}
866async function fetchImageBytes(src, signal) {
867  const combined = anySignal(signal, AbortSignal.timeout(FETCH_TIMEOUT_MS));
868  let res;
869  try {
870    res = await fetch(src, { signal: combined });
871  } catch (err) {
872    if (err instanceof DOMException && err.name === "TimeoutError") {
873      throw new Error("Image fetch timed out");
874    }
875    if (err instanceof DOMException && err.name === "AbortError") {
876      throw new Error("Image fetch aborted");
877    }
878    throw err;
879  }
880  if (!res.ok) throw new Error(`Fetch failed: HTTP ${res.status}`);
881  const length = Number(res.headers.get("content-length") ?? "0");
882  if (length > MAX_IMAGE_BYTES) {
883    throw new Error(`Image too large to inspect (${length} bytes)`);
884  }
885  const buf = await res.arrayBuffer();
886  if (buf.byteLength > MAX_IMAGE_BYTES) {
887    throw new Error(`Image too large to inspect (${buf.byteLength} bytes)`);
888  }
889  return new Uint8Array(buf);
890}
891function activeManifest(store) {
892  return store.manifests[store.manifests.length - 1];
893}
894function readClaimGenerator(claim) {
895  if (!claim) return void 0;
896  const info = claim["claim_generator_info"];
897  if (Array.isArray(info) && info.length > 0) {
898    const first = info[0];
899    if (first && typeof first === "object") {
900      const name = first["name"];
901      const version = first["version"];
902      if (typeof name === "string") {
903        return typeof version === "string" ? `${name} ${version}` : name;
904      }
905    }
906  }
907  const generator = claim["claim_generator"];
908  return typeof generator === "string" ? generator : void 0;
909}
910function readTitle(claim) {
911  if (!claim) return void 0;
912  const title = claim["dc:title"] ?? claim["title"];
913  return typeof title === "string" ? title : void 0;
914}
915function errorMessage(err) {
916  return err instanceof Error ? err.message : String(err);
917}
918function anySignal(optional, required) {
919  return optional ? AbortSignal.any([optional, required]) : required;
920}
921async function mapWithConcurrency(items, concurrency, fn) {
922  const results = new Array(items.length);
923  let next = 0;
924  const workers = [];
925  const limit = Math.max(1, Math.min(concurrency, items.length));
926  for (let w = 0; w < limit; w++) {
927    workers.push(
928      (async () => {
929        for (; ; ) {
930          const i = next++;
931          if (i >= items.length) return;
932          const item = items[i];
933          if (item === void 0) continue;
934          results[i] = await fn(item);
935        }
936      })()
937    );
938  }
939  await Promise.all(workers);
940  return results;
941}
942
943// src/bridge/packs/c2pa/index.ts
944var TOOLS = [
945  {
946    name: "scan_images_c2pa",
947    description: "Scan every <img> on the current page for C2PA Content Credentials (content provenance metadata) and return a per-image summary: whether a manifest is present, the claim generator (the tool that produced or edited the image), the title, and the signer name. Use this to find which images on a page carry provenance data. Note: provenance is decoded but NOT cryptographically verified.",
948    inputSchema: {
949      type: "object",
950      properties: {
951        limit: {
952          type: "integer",
953          minimum: 1,
954          maximum: 100,
955          default: 25,
956          description: "Maximum number of images to fetch and scan (default 25, max 100)."
957        }
958      },
959      required: []
960    }
961  },
962  {
963    name: "inspect_image_c2pa",
964    description: "Fetch a single image (by CSS selector or URL) and return its fully decoded C2PA manifest store: every manifest's claim, assertions (e.g. c2pa.actions edit history, schema.org authorship), and the COSE signature details (algorithm and signing-certificate identity). Provide exactly one of `selector` or `url`. Note: the signature is decoded but NOT cryptographically verified.",
965    inputSchema: {
966      type: "object",
967      properties: {
968        selector: {
969          type: "string",
970          maxLength: 8192,
971          description: "CSS selector for an <img> element on the page."
972        },
973        url: {
974          type: "string",
975          maxLength: 8192,
976          description: "Image URL (absolute, or relative to the current page) to fetch and inspect."
977        }
978      },
979      required: []
980    }
981  }
982];
983function optionalInt(args, key, min, max, fallback) {
984  if (typeof args !== "object" || args === null) return fallback;
985  const v = args[key];
986  if (v === void 0) return fallback;
987  if (typeof v !== "number" || !Number.isInteger(v)) return null;
988  if (v < min || v > max) return null;
989  return v;
990}
991var MAX_STRING_ARG = 8192;
992function readStringArg(args, key) {
993  if (typeof args !== "object" || args === null) return { kind: "absent" };
994  const v = args[key];
995  if (v === void 0 || v === null) return { kind: "absent" };
996  if (typeof v !== "string") {
997    return { kind: "invalid", reason: `\`${key}\` must be a string.` };
998  }
999  if (v.length === 0) return { kind: "absent" };
1000  if (v.length > MAX_STRING_ARG) {
1001    return {
1002      kind: "invalid",
1003      reason: `\`${key}\` exceeds the ${MAX_STRING_ARG}-character limit.`
1004    };
1005  }
1006  return { kind: "ok", value: v };
1007}
1008var handleScanImages = async (rawArgs, ctx) => {
1009  const limit = optionalInt(rawArgs, "limit", 1, 100, 25);
1010  if (limit === null) return errorResult("Invalid tool arguments");
1011  return ok({ ...await scanImages(limit, ctx.signal) });
1012};
1013var handleInspectImage = async (rawArgs, ctx) => {
1014  const selector = readStringArg(rawArgs, "selector");
1015  const url = readStringArg(rawArgs, "url");
1016  if (selector.kind === "invalid") return errorResult(selector.reason);
1017  if (url.kind === "invalid") return errorResult(url.reason);
1018  const hasSelector = selector.kind === "ok";
1019  const hasUrl = url.kind === "ok";
1020  if (!hasSelector && !hasUrl) {
1021    return errorResult("Provide either `selector` or `url`.");
1022  }
1023  if (hasSelector && hasUrl) {
1024    return errorResult("Provide only one of `selector` or `url`, not both.");
1025  }
1026  const result = await inspectImage(
1027    {
1028      ...selector.kind === "ok" ? { selector: selector.value } : {},
1029      ...url.kind === "ok" ? { url: url.value } : {}
1030    },
1031    ctx.signal
1032  );
1033  return ok({ ...result });
1034};
1035function wrapErrors(h) {
1036  return async (args, ctx) => {
1037    try {
1038      return await h(args, ctx);
1039    } catch (err) {
1040      return errorResult(err instanceof Error ? err.message : String(err));
1041    }
1042  };
1043}
1044var c2paPack = {
1045  kind: "static",
1046  name: "c2pa",
1047  tools: TOOLS,
1048  handlers: {
1049    scan_images_c2pa: wrapErrors(handleScanImages),
1050    inspect_image_c2pa: wrapErrors(handleInspectImage)
1051  }
1052};
1053
1054// src/bridge/packs/mcp-server-client/mcp-client.ts
1055var MCP_TIMEOUT_MS = 2e4;
1056var MCP_MAX_BYTES = 8 * 1024 * 1024;
1057var MCP_MAX_LIST_PAGES = 20;
1058var RPC_ID = 1;
1059function isObject(v) {
1060  return typeof v === "object" && v !== null && !Array.isArray(v);
1061}
1062async function readBoundedText(res) {
1063  const body = res.body;
1064  if (!body) return res.text();
1065  const reader = body.getReader();
1066  const chunks = [];
1067  let total = 0;
1068  try {
1069    for (; ; ) {
1070      const { done, value } = await reader.read();
1071      if (done) break;
1072      if (value) {
1073        total += value.byteLength;
1074        if (total > MCP_MAX_BYTES) {
1075          throw new Error(
1076            `MCP response exceeded ${MCP_MAX_BYTES} bytes; aborting.`
1077          );
1078        }
1079        chunks.push(value);
1080      }
1081    }
1082  } finally {
1083    await reader.cancel().catch(() => {
1084    });
1085  }
1086  const merged = new Uint8Array(total);
1087  let offset = 0;
1088  for (const chunk of chunks) {
1089    merged.set(chunk, offset);
1090    offset += chunk.byteLength;
1091  }
1092  return new TextDecoder().decode(merged);
1093}
1094function callSignal(signal) {
1095  const timeout = AbortSignal.timeout(MCP_TIMEOUT_MS);
1096  return signal ? AbortSignal.any([signal, timeout]) : timeout;
1097}
1098async function mcpRpc(endpoint, method, params, signal) {
1099  const res = await fetch(endpoint, {
1100    method: "POST",
1101    headers: {
1102      "content-type": "application/json",
1103      accept: "application/json, text/event-stream"
1104    },
1105    // Same-origin endpoint: authenticate as the logged-in user via
1106    // their ambient session cookie. Never forward credentials
1107    // cross-origin.
1108    credentials: "same-origin",
1109    body: JSON.stringify({ jsonrpc: "2.0", id: RPC_ID, method, params }),
1110    signal: callSignal(signal)
1111  });
1112  if (!res.ok) {
1113    throw new Error(`MCP endpoint returned HTTP ${res.status}`);
1114  }
1115  const contentType = res.headers.get("content-type") ?? "";
1116  const rpc = contentType.includes("text/event-stream") ? await readEventStreamResponse(res) : parseJson(await readBoundedText(res));
1117  return extractResult(rpc);
1118}
1119async function mcpListTools(endpoint, signal) {
1120  const out = [];
1121  let cursor;
1122  for (let page = 0; page < MCP_MAX_LIST_PAGES; page++) {
1123    const params = cursor ? { cursor } : {};
1124    const result = await mcpRpc(endpoint, "tools/list", params, signal);
1125    if (!Array.isArray(result.tools)) {
1126      throw new Error("MCP tools/list response has no tools array");
1127    }
1128    for (const t of result.tools) {
1129      const def = toToolDef(t);
1130      if (def) out.push(def);
1131    }
1132    cursor = typeof result.nextCursor === "string" && result.nextCursor.length > 0 ? result.nextCursor : void 0;
1133    if (!cursor) break;
1134  }
1135  return out;
1136}
1137function toToolDef(t) {
1138  if (!isObject(t)) return null;
1139  if (typeof t.name !== "string" || t.name.trim().length === 0) return null;
1140  const def = { name: t.name };
1141  if (typeof t.description === "string" && t.description.trim().length > 0) {
1142    def.description = t.description;
1143  }
1144  if (isObject(t.inputSchema) && t.inputSchema["type"] === "object") {
1145    def.inputSchema = t.inputSchema;
1146  }
1147  return def;
1148}
1149async function mcpCallTool(endpoint, name, args, signal) {
1150  const result = await mcpRpc(
1151    endpoint,
1152    "tools/call",
1153    { name, arguments: args },
1154    signal
1155  );
1156  if (!Array.isArray(result.content)) {
1157    throw new Error("MCP tools/call result has no content array");
1158  }
1159  const out = {
1160    // Drop any structurally-malformed blocks so a misbehaving site tool
1161    // can't push an unstructured payload straight into an agent response.
1162    content: result.content.filter(isContentBlock),
1163    isError: result.isError === true
1164  };
1165  if (isObject(result.structuredContent)) {
1166    out.structuredContent = result.structuredContent;
1167  }
1168  return out;
1169}
1170function isContentBlock(b) {
1171  if (!isObject(b) || typeof b["type"] !== "string") return false;
1172  if (b["type"] === "text") return typeof b["text"] === "string";
1173  return true;
1174}
1175function parseJson(raw) {
1176  try {
1177    return JSON.parse(raw);
1178  } catch {
1179    throw new Error("MCP endpoint returned a non-JSON response");
1180  }
1181}
1182async function readEventStreamResponse(res) {
1183  const body = res.body;
1184  if (!body) return parseEventStream(await res.text());
1185  const reader = body.getReader();
1186  const decoder = new TextDecoder();
1187  let buffer = "";
1188  let total = 0;
1189  try {
1190    for (; ; ) {
1191      const { done, value } = await reader.read();
1192      if (done) break;
1193      if (!value) continue;
1194      total += value.byteLength;
1195      if (total > MCP_MAX_BYTES) {
1196        throw new Error(`MCP response exceeded ${MCP_MAX_BYTES} bytes; aborting.`);
1197      }
1198      buffer += decoder.decode(value, { stream: true });
1199      const lines = buffer.split("\n");
1200      buffer = lines.pop() ?? "";
1201      for (const rawLine of lines) {
1202        const env2 = tryParseDataLine(rawLine);
1203        if (env2 !== void 0) return env2;
1204      }
1205    }
1206    const env = tryParseDataLine(buffer);
1207    if (env !== void 0) return env;
1208  } finally {
1209    await reader.cancel().catch(() => {
1210    });
1211  }
1212  throw new Error("MCP event stream contained no JSON-RPC response");
1213}
1214function parseEventStream(raw) {
1215  for (const rawLine of raw.split(/\r?\n/)) {
1216    const env = tryParseDataLine(rawLine);
1217    if (env !== void 0) return env;
1218  }
1219  throw new Error("MCP event stream contained no JSON-RPC response");
1220}
1221function tryParseDataLine(rawLine) {
1222  const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
1223  if (!line.startsWith("data:")) return void 0;
1224  const payload = line.slice("data:".length).trim();
1225  if (!payload) return void 0;
1226  try {
1227    const parsed = JSON.parse(payload);
1228    if (isResponseEnvelope(parsed)) return parsed;
1229  } catch {
1230  }
1231  return void 0;
1232}
1233function isResponseEnvelope(v) {
1234  if (!isObject(v) || v["jsonrpc"] !== "2.0") return false;
1235  const id = v["id"];
1236  if (id === RPC_ID) return true;
1237  if (id === null || id === void 0) {
1238    return ("result" in v || "error" in v) && !("method" in v);
1239  }
1240  return false;
1241}
1242function extractResult(rpc) {
1243  if (!isObject(rpc)) {
1244    throw new Error("Malformed JSON-RPC response from MCP endpoint");
1245  }
1246  if ("error" in rpc && rpc.error) {
1247    const err = rpc.error;
1248    const message = isObject(err) && typeof err.message === "string" ? err.message : "unknown error";
1249    throw new Error(`MCP endpoint error: ${message}`);
1250  }
1251  const result = rpc.result;
1252  if (!isObject(result)) {
1253    throw new Error("MCP response missing result");
1254  }
1255  return result;
1256}
1257
1258// src/bridge/packs/mcp-server-client/index.ts
1259function isObject2(v) {
1260  return typeof v === "object" && v !== null;
1261}
1262function makeHandler(endpoint, toolName) {
1263  return async (rawArgs, ctx) => {
1264    const args = isObject2(rawArgs) ? rawArgs : {};
1265    try {
1266      const result = await mcpCallTool(endpoint, toolName, args, ctx.signal);
1267      const out = {
1268        content: result.content
1269      };
1270      if (isObject2(result.structuredContent)) {
1271        out.structuredContent = result.structuredContent;
1272      }
1273      if (result.isError) out.isError = true;
1274      return customResult(out);
1275    } catch (err) {
1276      return errorResult(err instanceof Error ? err.message : String(err));
1277    }
1278  };
1279}
1280async function resolve(ctx) {
1281  const endpoint = ctx.mcpUrl;
1282  if (!endpoint) {
1283    return { tools: [], handlers: {} };
1284  }
1285  let defs;
1286  try {
1287    defs = await mcpListTools(endpoint, ctx.signal);
1288  } catch (err) {
1289    console.warn(
1290      `[webmcp-interceptor] mcp-server-client: tools/list failed for "${endpoint}"; registering no site tools.`,
1291      err
1292    );
1293    return { tools: [], handlers: {} };
1294  }
1295  const tools = [];
1296  const handlers = {};
1297  for (const def of defs) {
1298    tools.push({
1299      name: def.name,
1300      // WebMCP requires a non-empty description; fall back to the name
1301      // when it's missing OR empty (`??` alone would preserve an empty
1302      // string, which fails registration).
1303      description: def.description || def.name,
1304      inputSchema: def.inputSchema ?? {
1305        type: "object",
1306        properties: {}
1307      }
1308    });
1309    handlers[def.name] = makeHandler(endpoint, def.name);
1310  }
1311  console.debug(
1312    `[webmcp-interceptor] mcp-server-client: registered ${tools.length} site tool(s) from ${endpoint}.`
1313  );
1314  return { tools, handlers };
1315}
1316var mcpServerClientPack = {
1317  kind: "dynamic",
1318  name: "mcp-server-client",
1319  resolve
1320};
1321
1322// src/bridge/bridge.ts
1323var ALL_PACKS = [c2paPack, mcpServerClientPack];
1324var DEFAULT_ACTIVE_PACKS = [];
1325function resolveOwnScript() {
1326  const candidates = Array.from(
1327    document.querySelectorAll("script[src]")
1328  );
1329  try {
1330    const selfUrl = import.meta.url;
1331    const exact = candidates.find((s) => s.src === selfUrl);
1332    if (exact) return exact;
1333  } catch {
1334  }
1335  return candidates.find((s) => s.src.includes("/.webmcp/bridge.js")) ?? null;
1336}
1337var script = resolveOwnScript();
1338initBridge().catch((err) => {
1339  console.error("[webmcp-interceptor] Bridge initialisation failed:", err);
1340});
1341async function initBridge() {
1342  const mc = resolveModelContext();
1343  if (!mc) {
1344    console.warn(
1345      "[webmcp-interceptor] document.modelContext is not available.\nTo enable WebMCP, use Chrome M146+ and enable:\nchrome://flags/#enable-experimental-web-platform-features"
1346    );
1347    return;
1348  }
1349  const workerBaseUrl = resolveWorkerBaseUrl();
1350  const mcpUrl = normalizeMcpUrl(script?.dataset["mcpUrl"]);
1351  const ctx = {
1352    origin: window.location.origin,
1353    workerRpc: workerBaseUrl ? makeWorkerRpc(workerBaseUrl) : workerRpcUnavailable,
1354    ...mcpUrl ? { mcpUrl } : {}
1355  };
1356  const activePacks = selectActivePacks(
1357    ALL_PACKS,
1358    parsePackList(script?.dataset["packs"]),
1359    DEFAULT_ACTIVE_PACKS
1360  );
1361  const staticResolved = resolveStaticPacks(activePacks);
1362  const staticRegistry = buildRegistry(staticResolved);
1363  registerTools(mc, ctx, staticRegistry);
1364  const registeredNames = new Set(staticRegistry.tools.map((t) => t.name));
1365  console.debug(
1366    `[webmcp-interceptor] Registered ${staticRegistry.tools.length} static tool(s); resolving dynamic packs\u2026`
1367  );
1368  const dynamicResolved = await resolveDynamicPacks(activePacks, ctx);
1369  if (dynamicResolved.length > 0) {
1370    const combined = buildRegistry([...staticResolved, ...dynamicResolved]);
1371    const additions = {
1372      tools: combined.tools.filter((t) => !registeredNames.has(t.name)),
1373      handlers: combined.handlers
1374    };
1375    registerTools(mc, ctx, additions);
1376    console.debug(
1377      `[webmcp-interceptor] Registered ${additions.tools.length} dynamic tool(s) from ${dynamicResolved.length} dynamic pack(s).`
1378    );
1379  }
1380}
1381function registerTools(mc, ctx, registry) {
1382  for (const tool of registry.tools) {
1383    const handler = registry.handlers[tool.name];
1384    if (!handler) continue;
1385    mc.registerTool({
1386      name: tool.name,
1387      // MCP's `Tool.description` is optional but WebMCP's
1388      // `registerTool()` requires a non-empty string. Fall back to
1389      // the tool name when the description is missing OR empty (`??`
1390      // alone would let an empty string through) so registration never
1391      // fails — the agent at least sees the name.
1392      description: tool.description || tool.name,
1393      inputSchema: tool.inputSchema,
1394      // WebMCP may pass an options bag with an AbortSignal as the
1395      // second argument; forward it so handlers can cancel in-flight
1396      // work. When absent, the per-call context simply has no signal.
1397      execute: (args, options) => invokeHandler(tool.name, handler, args, {
1398        ...ctx,
1399        ...options?.signal ? { signal: options.signal } : {}
1400      })
1401    });
1402  }
1403}
1404async function invokeHandler(toolName, handler, args, ctx) {
1405  let result;
1406  try {
1407    result = await handler(args, ctx);
1408  } catch (err) {
1409    const message = err instanceof Error ? err.message : String(err);
1410    console.warn(`[webmcp-interceptor] Tool "${toolName}" threw:`, message);
1411    throw new Error(message);
1412  }
1413  if (result.isError) {
1414    const text = extractText(result) ?? `${toolName} failed`;
1415    const error = new Error(text, { cause: result });
1416    error.webmcpResult = result;
1417    throw error;
1418  }
1419  if (result.structuredContent !== void 0) return result.structuredContent;
1420  return extractText(result) ?? null;
1421}
1422function extractText(r) {
1423  if (!r.content || r.content.length === 0) return null;
1424  const texts = [];
1425  for (const block of r.content) {
1426    if (block.type === "text") texts.push(block.text);
1427  }
1428  return texts.length === 0 ? null : texts.join("\n");
1429}
1430function resolveWorkerBaseUrl() {
1431  let src = script?.src;
1432  if (!src) {
1433    try {
1434      src = import.meta.url;
1435    } catch {
1436      src = void 0;
1437    }
1438  }
1439  if (!src) return null;
1440  try {
1441    const url = new URL(src);
1442    const webmcpIndex = url.pathname.indexOf("/.webmcp/");
1443    if (webmcpIndex >= 0) {
1444      url.pathname = url.pathname.slice(0, webmcpIndex);
1445    }
1446    url.search = "";
1447    url.hash = "";
1448    return url.toString().replace(/\/$/, "");
1449  } catch {
1450    return null;
1451  }
1452}
1453function makeWorkerRpc(baseUrl) {
1454  return async (path, body, validate) => {
1455    const url = `${baseUrl}/.webmcp/rpc/${path.replace(/^\//, "")}`;
1456    const res = await fetch(url, {
1457      method: "POST",
1458      headers: { "content-type": "application/json" },
1459      // Important: NOT `credentials: 'include'`. The worker is on a
1460      // different origin and we don't want to forward the page's
1461      // cookies to it. Each pack is responsible for sending whatever
1462      // worker-specific auth it needs (today, none).
1463      body: JSON.stringify(body)
1464    });
1465    const text = await res.text();
1466    let parsed;
1467    try {
1468      parsed = text === "" ? null : JSON.parse(text);
1469    } catch {
1470      throw new Error(`Worker returned non-JSON response (HTTP ${res.status})`);
1471    }
1472    if (!res.ok) {
1473      const message = parsed && typeof parsed === "object" && "error" in parsed && typeof parsed.error === "string" ? parsed.error : `Worker RPC failed (HTTP ${res.status})`;
1474      throw new Error(message);
1475    }
1476    return validate(parsed);
1477  };
1478}
1479function workerRpcUnavailable() {
1480  throw new Error(
1481    "Worker base URL could not be determined. Worker-backed tools are unavailable on this page."
1482  );
1483}
1484function resolveModelContext() {
1485  const fromDocument = document.modelContext;
1486  if (fromDocument) return fromDocument;
1487  const fromNavigator = navigator.modelContext;
1488  return fromNavigator ?? null;
1489}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.