@mcp-b/webmcp-polyfill 4.0.0 → 5.0.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.
- package/README.md +123 -193
- package/dist/index.d.ts +7 -23
- package/dist/index.iife.js +1 -2
- package/dist/index.js +940 -373
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +22 -12
- package/dist/schema.js +208 -101
- package/dist/schema.js.map +1 -1
- package/package.json +10 -13
- package/dist/index.d.ts.map +0 -1
- package/dist/schema.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,368 +1,943 @@
|
|
|
1
|
-
import { isPlainObject,
|
|
1
|
+
import { coerceWebMcpToolDescriptor, createInvalidStateError, createToolInvocationFailedError, createUnknownError, isPlainObject, parseChromeToolInput, serializeChromeToolResult, serializeInputSchema, toWebMcpAnnotations, validateExecutableOrigin, validatePotentiallyTrustworthyOrigins, validateWebMcpAccess, validateWebMcpToolDescriptor, withAbortSignal } from "./schema.js";
|
|
2
2
|
|
|
3
|
+
//#region src/declarative-forms.ts
|
|
4
|
+
const agentInvokedEvents = /* @__PURE__ */ new WeakSet();
|
|
5
|
+
const agentResponses = /* @__PURE__ */ new WeakMap();
|
|
6
|
+
const activeSubmissions = /* @__PURE__ */ new WeakMap();
|
|
7
|
+
function isAgentInvokedSubmitEvent(event) {
|
|
8
|
+
return event.isTrusted && (agentInvokedEvents.has(event) || event.eventPhase !== Event.NONE && event.target instanceof HTMLFormElement && activeSubmissions.has(event.target));
|
|
9
|
+
}
|
|
10
|
+
function respondWithAgentSubmitEvent(event, agentResponse) {
|
|
11
|
+
if (!isAgentInvokedSubmitEvent(event)) throw new DOMException("respondWith() is only available during an agent-invoked submit event", "InvalidStateError");
|
|
12
|
+
if (!event.defaultPrevented) throw new DOMException("respondWith() requires preventDefault() during an agent-invoked submit event", "InvalidStateError");
|
|
13
|
+
if (event.eventPhase === Event.NONE) throw new DOMException("respondWith() is only available while the submit event is being dispatched", "InvalidStateError");
|
|
14
|
+
agentInvokedEvents.add(event);
|
|
15
|
+
const response = Promise.resolve(agentResponse);
|
|
16
|
+
agentResponses.set(event, response);
|
|
17
|
+
if (event.target instanceof HTMLFormElement) activeSubmissions.get(event.target)?.respond(event);
|
|
18
|
+
}
|
|
19
|
+
const TEXT_INPUT_TYPES = new Set([
|
|
20
|
+
"email",
|
|
21
|
+
"password",
|
|
22
|
+
"search",
|
|
23
|
+
"tel",
|
|
24
|
+
"text",
|
|
25
|
+
"url"
|
|
26
|
+
]);
|
|
27
|
+
const READONLY_INPUT_TYPES = new Set([
|
|
28
|
+
...TEXT_INPUT_TYPES,
|
|
29
|
+
"date",
|
|
30
|
+
"datetime-local",
|
|
31
|
+
"month",
|
|
32
|
+
"number",
|
|
33
|
+
"time",
|
|
34
|
+
"week"
|
|
35
|
+
]);
|
|
36
|
+
function isControl(element) {
|
|
37
|
+
return element instanceof HTMLInputElement || element instanceof HTMLSelectElement || element instanceof HTMLTextAreaElement;
|
|
38
|
+
}
|
|
39
|
+
function getFormControls(form) {
|
|
40
|
+
return Reflect.get(HTMLFormElement.prototype, "elements", form);
|
|
41
|
+
}
|
|
42
|
+
function getFormAttribute(form, name) {
|
|
43
|
+
return Reflect.apply(Element.prototype.getAttribute, form, [name]);
|
|
44
|
+
}
|
|
45
|
+
function formHasAttribute(form, name) {
|
|
46
|
+
return Reflect.apply(Element.prototype.hasAttribute, form, [name]);
|
|
47
|
+
}
|
|
48
|
+
function isFormConnected(form) {
|
|
49
|
+
return Reflect.get(Node.prototype, "isConnected", form);
|
|
50
|
+
}
|
|
51
|
+
function getOpenShadowRoot(element) {
|
|
52
|
+
return Reflect.get(Element.prototype, "shadowRoot", element);
|
|
53
|
+
}
|
|
54
|
+
function checkFormValidity(form) {
|
|
55
|
+
return Reflect.apply(HTMLFormElement.prototype.checkValidity, form, []);
|
|
56
|
+
}
|
|
57
|
+
function requestFormSubmit(form, submitter) {
|
|
58
|
+
Reflect.apply(HTMLFormElement.prototype.requestSubmit, form, submitter ? [submitter] : []);
|
|
59
|
+
}
|
|
60
|
+
function getControls(form) {
|
|
61
|
+
return [...getFormControls(form)].filter((element) => isControl(element) && !element.matches(":disabled") && !("readOnly" in element && element.readOnly && (element instanceof HTMLTextAreaElement || READONLY_INPUT_TYPES.has(element.type))));
|
|
62
|
+
}
|
|
63
|
+
function controlGroups(form) {
|
|
64
|
+
const groups = /* @__PURE__ */ new Map();
|
|
65
|
+
for (const control of getControls(form)) {
|
|
66
|
+
const name = control.name.trim();
|
|
67
|
+
const controls = groups.get(name);
|
|
68
|
+
if (controls) controls.push(control);
|
|
69
|
+
else groups.set(name, [control]);
|
|
70
|
+
}
|
|
71
|
+
return groups;
|
|
72
|
+
}
|
|
73
|
+
function labelText(control) {
|
|
74
|
+
return [...control.labels ?? []].map((label) => {
|
|
75
|
+
const copy = label.cloneNode(true);
|
|
76
|
+
if (!(copy instanceof HTMLElement)) return "";
|
|
77
|
+
copy.querySelectorAll("button, input, meter, output, progress, select, textarea").forEach((element) => element.remove());
|
|
78
|
+
return copy.textContent?.trim() ?? "";
|
|
79
|
+
}).filter(Boolean).join("; ");
|
|
80
|
+
}
|
|
81
|
+
function commonFieldset(form, controls) {
|
|
82
|
+
for (let element = controls[0]?.parentElement; element && element !== form; element = element.parentElement) if (element instanceof HTMLFieldSetElement && controls.every((control) => element.contains(control))) return element;
|
|
83
|
+
}
|
|
84
|
+
function parameterDescription(form, controls) {
|
|
85
|
+
if (controls.length === 1) {
|
|
86
|
+
const control = controls[0];
|
|
87
|
+
if (!control) return void 0;
|
|
88
|
+
return control.getAttribute("toolparamdescription") || labelText(control) || control.getAttribute("aria-description") || void 0;
|
|
89
|
+
}
|
|
90
|
+
return commonFieldset(form, controls)?.getAttribute("toolparamdescription") || void 0;
|
|
91
|
+
}
|
|
92
|
+
function withDescription(schema, form, controls, extra) {
|
|
93
|
+
const description = parameterDescription(form, controls);
|
|
94
|
+
const combined = description && extra ? `${description} (${extra})` : description || extra;
|
|
95
|
+
return combined ? {
|
|
96
|
+
...schema,
|
|
97
|
+
description: combined
|
|
98
|
+
} : schema;
|
|
99
|
+
}
|
|
100
|
+
function validNumberAttribute(input, name) {
|
|
101
|
+
const raw = input.getAttribute(name);
|
|
102
|
+
if (raw === null || raw.trim() === "") return void 0;
|
|
103
|
+
const value = Number(raw);
|
|
104
|
+
return Number.isFinite(value) ? value : void 0;
|
|
105
|
+
}
|
|
106
|
+
function isStepBaseMultiple(stepBase, step) {
|
|
107
|
+
const quotient = stepBase / step;
|
|
108
|
+
return Math.abs(quotient - Math.round(quotient)) < Number.EPSILON * 16;
|
|
109
|
+
}
|
|
110
|
+
function validPattern(input) {
|
|
111
|
+
const pattern = input.getAttribute("pattern");
|
|
112
|
+
if (pattern === null) return void 0;
|
|
113
|
+
try {
|
|
114
|
+
new RegExp(pattern, "v");
|
|
115
|
+
return pattern;
|
|
116
|
+
} catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function numberSchema(input, includePattern = true) {
|
|
121
|
+
const schema = { type: "number" };
|
|
122
|
+
const minimum = validNumberAttribute(input, "min");
|
|
123
|
+
const maximum = validNumberAttribute(input, "max");
|
|
124
|
+
if (minimum !== void 0) schema.minimum = minimum;
|
|
125
|
+
if (maximum !== void 0) schema.maximum = maximum;
|
|
126
|
+
const rawStep = input.getAttribute("step");
|
|
127
|
+
if (rawStep !== "any") {
|
|
128
|
+
const parsedStep = rawStep === null || rawStep === "" ? 1 : Number(rawStep);
|
|
129
|
+
const step = Number.isFinite(parsedStep) && parsedStep > 0 ? parsedStep : 1;
|
|
130
|
+
const rawValue = Number(input.getAttribute("value"));
|
|
131
|
+
if (isStepBaseMultiple(minimum ?? (Number.isFinite(rawValue) ? rawValue : 0), step)) schema.multipleOf = step;
|
|
132
|
+
}
|
|
133
|
+
const pattern = includePattern ? validPattern(input) : void 0;
|
|
134
|
+
if (pattern !== void 0) schema.pattern = pattern;
|
|
135
|
+
return schema;
|
|
136
|
+
}
|
|
137
|
+
function temporalFormat(input, datePrefix) {
|
|
138
|
+
const rawStep = input.getAttribute("step");
|
|
139
|
+
const parsedStep = rawStep === null || rawStep === "" ? 60 : Number(rawStep);
|
|
140
|
+
const step = Number.isFinite(parsedStep) && parsedStep > 0 ? parsedStep : 60;
|
|
141
|
+
if (step < 1) return `${datePrefix}(:[0-5][0-9](\\.[0-9]{1,3})?)?$`;
|
|
142
|
+
if (step < 60) return `${datePrefix}(:[0-5][0-9])?$`;
|
|
143
|
+
return `${datePrefix}$`;
|
|
144
|
+
}
|
|
145
|
+
function optionSchemas(options) {
|
|
146
|
+
return {
|
|
147
|
+
anyOf: options.map((option) => ({
|
|
148
|
+
type: "string",
|
|
149
|
+
const: option.value,
|
|
150
|
+
title: option.textContent ?? ""
|
|
151
|
+
})),
|
|
152
|
+
enum: options.map((option) => option.value)
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function groupChoiceSchemas(controls) {
|
|
156
|
+
return {
|
|
157
|
+
anyOf: controls.map((control) => {
|
|
158
|
+
const title = labelText(control);
|
|
159
|
+
return {
|
|
160
|
+
type: "string",
|
|
161
|
+
const: control.value,
|
|
162
|
+
...title ? { title } : {}
|
|
163
|
+
};
|
|
164
|
+
}),
|
|
165
|
+
enum: controls.map((control) => control.value)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function parameterSchema(form, controls) {
|
|
169
|
+
const first = controls[0];
|
|
170
|
+
if (!first) return void 0;
|
|
171
|
+
if (controls.length > 1) {
|
|
172
|
+
if (!controls.every((control) => control instanceof HTMLInputElement)) return void 0;
|
|
173
|
+
if (controls.every((control) => control.type === "checkbox")) return withDescription({
|
|
174
|
+
type: "array",
|
|
175
|
+
items: {
|
|
176
|
+
type: "string",
|
|
177
|
+
...groupChoiceSchemas(controls)
|
|
178
|
+
},
|
|
179
|
+
uniqueItems: true
|
|
180
|
+
}, form, controls);
|
|
181
|
+
if (controls.every((control) => control.type === "radio")) return withDescription({
|
|
182
|
+
type: "string",
|
|
183
|
+
...groupChoiceSchemas(controls)
|
|
184
|
+
}, form, controls);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (first instanceof HTMLTextAreaElement) return withDescription({ type: "string" }, form, controls);
|
|
188
|
+
if (first instanceof HTMLSelectElement) {
|
|
189
|
+
const choices = optionSchemas([...first.options]);
|
|
190
|
+
return withDescription(first.multiple ? {
|
|
191
|
+
type: "array",
|
|
192
|
+
items: {
|
|
193
|
+
type: "string",
|
|
194
|
+
...choices
|
|
195
|
+
},
|
|
196
|
+
uniqueItems: true
|
|
197
|
+
} : {
|
|
198
|
+
type: "string",
|
|
199
|
+
...choices
|
|
200
|
+
}, form, controls);
|
|
201
|
+
}
|
|
202
|
+
if (TEXT_INPUT_TYPES.has(first.type)) {
|
|
203
|
+
const schema = { type: "string" };
|
|
204
|
+
const pattern = validPattern(first);
|
|
205
|
+
if (pattern !== void 0) schema.pattern = pattern;
|
|
206
|
+
return withDescription(schema, form, controls);
|
|
207
|
+
}
|
|
208
|
+
if (first.type === "hidden") return first.getAttribute("toolparamdescription") ? withDescription({ type: "string" }, form, controls) : void 0;
|
|
209
|
+
if (first.type === "number") return withDescription(numberSchema(first), form, controls);
|
|
210
|
+
if (first.type === "range") {
|
|
211
|
+
const schema = numberSchema(first, false);
|
|
212
|
+
schema.minimum ??= 0;
|
|
213
|
+
schema.maximum ??= 100;
|
|
214
|
+
return withDescription(schema, form, controls);
|
|
215
|
+
}
|
|
216
|
+
if (first.type === "checkbox") return withDescription({ type: "boolean" }, form, controls);
|
|
217
|
+
if (first.type === "radio") return withDescription({
|
|
218
|
+
type: "string",
|
|
219
|
+
...groupChoiceSchemas([first])
|
|
220
|
+
}, form, controls);
|
|
221
|
+
if (first.type === "date") return withDescription({
|
|
222
|
+
type: "string",
|
|
223
|
+
format: "date"
|
|
224
|
+
}, form, controls, "Dates MUST be provided in 'YYYY-MM-DD' format.");
|
|
225
|
+
if (first.type === "month") return withDescription({
|
|
226
|
+
type: "string",
|
|
227
|
+
format: "^[0-9]{4}-(0[1-9]|1[0-2])$"
|
|
228
|
+
}, form, controls);
|
|
229
|
+
if (first.type === "week") return withDescription({
|
|
230
|
+
type: "string",
|
|
231
|
+
format: "^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$"
|
|
232
|
+
}, form, controls);
|
|
233
|
+
if (first.type === "time") return withDescription({
|
|
234
|
+
type: "string",
|
|
235
|
+
format: temporalFormat(first, "^([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
236
|
+
}, form, controls);
|
|
237
|
+
if (first.type === "datetime-local") return withDescription({
|
|
238
|
+
type: "string",
|
|
239
|
+
format: temporalFormat(first, "^[0-9]{4}-(0[1-9]|1[0-2])-[0-9]{2}T([01][0-9]|2[0-3]):[0-5][0-9]")
|
|
240
|
+
}, form, controls);
|
|
241
|
+
if (first.type === "color") return withDescription({
|
|
242
|
+
type: "string",
|
|
243
|
+
format: "^#[0-9a-zA-Z]{6}$"
|
|
244
|
+
}, form, controls);
|
|
245
|
+
}
|
|
246
|
+
function synthesizeSchema(form) {
|
|
247
|
+
const properties = {};
|
|
248
|
+
const required = [];
|
|
249
|
+
for (const [name, controls] of controlGroups(form)) {
|
|
250
|
+
if (!name) continue;
|
|
251
|
+
const schema = parameterSchema(form, controls);
|
|
252
|
+
if (!schema) continue;
|
|
253
|
+
Object.defineProperty(properties, name, {
|
|
254
|
+
configurable: true,
|
|
255
|
+
enumerable: true,
|
|
256
|
+
value: schema,
|
|
257
|
+
writable: true
|
|
258
|
+
});
|
|
259
|
+
if (controls.some((control) => control.required)) required.push(name);
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
type: "object",
|
|
263
|
+
properties,
|
|
264
|
+
required
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function toolDefinition(form) {
|
|
268
|
+
return {
|
|
269
|
+
name: getFormAttribute(form, "toolname") ?? "",
|
|
270
|
+
title: getFormAttribute(form, "tooltitle") ?? "",
|
|
271
|
+
description: getFormAttribute(form, "tooldescription") ?? "",
|
|
272
|
+
inputSchema: synthesizeSchema(form),
|
|
273
|
+
autosubmit: formHasAttribute(form, "toolautosubmit")
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function toFormString(value) {
|
|
277
|
+
if (typeof value === "string" || typeof value === "boolean") return String(value);
|
|
278
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
279
|
+
}
|
|
280
|
+
function toFormBoolean(value) {
|
|
281
|
+
if (typeof value === "boolean") return value;
|
|
282
|
+
if (typeof value === "number" && Number.isInteger(value)) return value !== 0;
|
|
283
|
+
if (typeof value !== "string") return void 0;
|
|
284
|
+
if (value === "1" || value.toLowerCase() === "true") return true;
|
|
285
|
+
if (value === "0" || value.toLowerCase() === "false") return false;
|
|
286
|
+
}
|
|
287
|
+
function hasUniqueAllowedValues(value, allowed) {
|
|
288
|
+
if (!Array.isArray(value)) return false;
|
|
289
|
+
const remaining = new Set(allowed);
|
|
290
|
+
for (const item of value) {
|
|
291
|
+
const string = toFormString(item);
|
|
292
|
+
if (string === void 0 || !remaining.delete(string)) return false;
|
|
293
|
+
}
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
function inputAcceptsValue(input, value) {
|
|
297
|
+
if (value === "") return input.type !== "number" && input.type !== "range";
|
|
298
|
+
const probe = input.ownerDocument.createElement("input");
|
|
299
|
+
probe.type = input.type;
|
|
300
|
+
probe.value = value;
|
|
301
|
+
return probe.value !== "";
|
|
302
|
+
}
|
|
303
|
+
function validatesParameter(form, controls, value) {
|
|
304
|
+
const first = controls[0];
|
|
305
|
+
if (!first || !parameterSchema(form, controls)) return false;
|
|
306
|
+
if (controls.length > 1) {
|
|
307
|
+
if (!controls.every((control) => control instanceof HTMLInputElement)) return false;
|
|
308
|
+
if (controls.every((control) => control.type === "checkbox")) return hasUniqueAllowedValues(value, new Set(controls.map((control) => control.value)));
|
|
309
|
+
if (controls.every((control) => control.type === "radio")) {
|
|
310
|
+
const string = toFormString(value);
|
|
311
|
+
return string !== void 0 && controls.some((control) => control.value === string);
|
|
312
|
+
}
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
if (first instanceof HTMLSelectElement) {
|
|
316
|
+
const allowed = new Set([...first.options].map((option) => option.value));
|
|
317
|
+
if (first.multiple) return hasUniqueAllowedValues(value, allowed);
|
|
318
|
+
const string = toFormString(value);
|
|
319
|
+
return string !== void 0 && allowed.has(string);
|
|
320
|
+
}
|
|
321
|
+
if (first instanceof HTMLTextAreaElement) return toFormString(value) !== void 0;
|
|
322
|
+
if (first.type === "checkbox") return toFormBoolean(value) !== void 0;
|
|
323
|
+
if (first.type === "radio") {
|
|
324
|
+
const string = toFormString(value);
|
|
325
|
+
return string !== void 0 && first.value === string;
|
|
326
|
+
}
|
|
327
|
+
const string = toFormString(value);
|
|
328
|
+
return string !== void 0 && inputAcceptsValue(first, string);
|
|
329
|
+
}
|
|
330
|
+
function dispatchInputAndChange(control) {
|
|
331
|
+
control.dispatchEvent(new Event("input", {
|
|
332
|
+
bubbles: true,
|
|
333
|
+
composed: true
|
|
334
|
+
}));
|
|
335
|
+
control.dispatchEvent(new Event("change", { bubbles: true }));
|
|
336
|
+
}
|
|
337
|
+
function setNativeValue(control, value) {
|
|
338
|
+
const prototype = control instanceof HTMLInputElement ? HTMLInputElement.prototype : HTMLTextAreaElement.prototype;
|
|
339
|
+
Object.getOwnPropertyDescriptor(prototype, "value")?.set?.call(control, value);
|
|
340
|
+
}
|
|
341
|
+
function setNativeChecked(control, checked) {
|
|
342
|
+
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set?.call(control, checked);
|
|
343
|
+
}
|
|
344
|
+
function fillParameter(controls, value) {
|
|
345
|
+
const first = controls[0];
|
|
346
|
+
if (!first) return;
|
|
347
|
+
if (controls.length > 1 && controls.every((control) => control instanceof HTMLInputElement)) {
|
|
348
|
+
if (controls.every((control) => control.type === "checkbox") && Array.isArray(value)) {
|
|
349
|
+
const checked = new Set(value.map(toFormString));
|
|
350
|
+
for (const control of controls) {
|
|
351
|
+
const next = checked.has(control.value);
|
|
352
|
+
if (control.checked === next) continue;
|
|
353
|
+
setNativeChecked(control, next);
|
|
354
|
+
dispatchInputAndChange(control);
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
const selected = toFormString(value);
|
|
359
|
+
const control = controls.find((candidate) => candidate.value === selected);
|
|
360
|
+
if (control && !control.checked) {
|
|
361
|
+
setNativeChecked(control, true);
|
|
362
|
+
dispatchInputAndChange(control);
|
|
363
|
+
}
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (first instanceof HTMLSelectElement) {
|
|
367
|
+
if (first.multiple && Array.isArray(value)) {
|
|
368
|
+
const selected = new Set(value.map(toFormString));
|
|
369
|
+
let changed = false;
|
|
370
|
+
for (const option of first.options) {
|
|
371
|
+
const next = selected.has(option.value);
|
|
372
|
+
if (option.selected === next) continue;
|
|
373
|
+
option.selected = next;
|
|
374
|
+
changed = true;
|
|
375
|
+
}
|
|
376
|
+
if (changed) dispatchInputAndChange(first);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const next = toFormString(value);
|
|
380
|
+
if (next !== void 0 && first.value !== next) {
|
|
381
|
+
first.value = next;
|
|
382
|
+
dispatchInputAndChange(first);
|
|
383
|
+
}
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
if (first instanceof HTMLInputElement && first.type === "checkbox") {
|
|
387
|
+
const next = toFormBoolean(value);
|
|
388
|
+
if (next !== void 0 && first.checked !== next) {
|
|
389
|
+
setNativeChecked(first, next);
|
|
390
|
+
dispatchInputAndChange(first);
|
|
391
|
+
}
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
if (first instanceof HTMLInputElement && first.type === "radio") {
|
|
395
|
+
if (toFormString(value) === first.value && !first.checked) {
|
|
396
|
+
setNativeChecked(first, true);
|
|
397
|
+
dispatchInputAndChange(first);
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
const next = toFormString(value);
|
|
402
|
+
if (next !== void 0 && first.value !== next) {
|
|
403
|
+
setNativeValue(first, next);
|
|
404
|
+
dispatchInputAndChange(first);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function fillForm(form, input) {
|
|
408
|
+
if (Array.isArray(input)) throw new TypeError("Declarative tool input must be an object");
|
|
409
|
+
const groups = controlGroups(form);
|
|
410
|
+
for (const [name, value] of Object.entries(input)) {
|
|
411
|
+
const controls = groups.get(name);
|
|
412
|
+
if (!controls || !validatesParameter(form, controls, value)) throw new TypeError(`Invalid value for declarative form parameter "${name}"`);
|
|
413
|
+
}
|
|
414
|
+
for (const [name, value] of Object.entries(input)) fillParameter(groups.get(name) ?? [], value);
|
|
415
|
+
}
|
|
416
|
+
function findSubmitter(form) {
|
|
417
|
+
return [...getFormControls(form)].find((element) => !element.matches(":disabled") && (element instanceof HTMLButtonElement && element.type === "submit" || element instanceof HTMLInputElement && ["image", "submit"].includes(element.type)));
|
|
418
|
+
}
|
|
419
|
+
function validationError(form) {
|
|
420
|
+
const failures = [...getFormControls(form)].filter((element) => isControl(element) && element.willValidate && !element.validity.valid).map((control) => `${control.name.trim() || "{unknown}"}: ${control.validationMessage}`).join(". ");
|
|
421
|
+
return new DOMException(`Form validation failed: ${failures}`, "UnknownError");
|
|
422
|
+
}
|
|
423
|
+
function toolActivatedEvent(toolName) {
|
|
424
|
+
const event = new Event("toolactivated");
|
|
425
|
+
Object.defineProperty(event, "toolName", {
|
|
426
|
+
enumerable: true,
|
|
427
|
+
value: toolName
|
|
428
|
+
});
|
|
429
|
+
return event;
|
|
430
|
+
}
|
|
431
|
+
function waitForSubmission(registration, toolName, autosubmit, submitter) {
|
|
432
|
+
const { form } = registration;
|
|
433
|
+
registration.cancelPending?.(new DOMException("Tool execution cancelled", "UnknownError"));
|
|
434
|
+
return new Promise((resolve, reject) => {
|
|
435
|
+
let settled = false;
|
|
436
|
+
const cleanup = () => {
|
|
437
|
+
Reflect.apply(EventTarget.prototype.removeEventListener, form, [
|
|
438
|
+
"invalid",
|
|
439
|
+
onInvalid,
|
|
440
|
+
true
|
|
441
|
+
]);
|
|
442
|
+
activeSubmissions.delete(form);
|
|
443
|
+
if (registration.cancelPending === cancel) delete registration.cancelPending;
|
|
444
|
+
};
|
|
445
|
+
const finish = (callback) => {
|
|
446
|
+
if (settled) return;
|
|
447
|
+
settled = true;
|
|
448
|
+
cleanup();
|
|
449
|
+
callback();
|
|
450
|
+
};
|
|
451
|
+
const cancel = (reason) => finish(() => reject(reason));
|
|
452
|
+
const settleResponse = (response) => {
|
|
453
|
+
response.then((value) => finish(() => resolve(value)), (error) => finish(() => reject(error)));
|
|
454
|
+
};
|
|
455
|
+
const onInvalid = (event) => {
|
|
456
|
+
if (!event.isTrusted) return;
|
|
457
|
+
queueMicrotask(() => {
|
|
458
|
+
if (!checkFormValidity(form)) cancel(validationError(form));
|
|
459
|
+
});
|
|
460
|
+
};
|
|
461
|
+
activeSubmissions.set(form, {
|
|
462
|
+
complete(event) {
|
|
463
|
+
queueMicrotask(() => {
|
|
464
|
+
const response = agentResponses.get(event);
|
|
465
|
+
if (response) settleResponse(response);
|
|
466
|
+
else if (event.defaultPrevented) cancel(new DOMException("preventDefault() requires respondWith()", "UnknownError"));
|
|
467
|
+
else finish(() => resolve(void 0));
|
|
468
|
+
});
|
|
469
|
+
},
|
|
470
|
+
direct() {
|
|
471
|
+
finish(() => resolve(void 0));
|
|
472
|
+
},
|
|
473
|
+
respond(event) {
|
|
474
|
+
queueMicrotask(() => {
|
|
475
|
+
const response = agentResponses.get(event);
|
|
476
|
+
if (response) settleResponse(response);
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
registration.cancelPending = cancel;
|
|
481
|
+
Reflect.apply(EventTarget.prototype.addEventListener, form, [
|
|
482
|
+
"invalid",
|
|
483
|
+
onInvalid,
|
|
484
|
+
true
|
|
485
|
+
]);
|
|
486
|
+
if (!autosubmit) {
|
|
487
|
+
submitter?.focus();
|
|
488
|
+
window.dispatchEvent(toolActivatedEvent(toolName));
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
requestFormSubmit(form, submitter);
|
|
493
|
+
window.dispatchEvent(toolActivatedEvent(toolName));
|
|
494
|
+
} catch (error) {
|
|
495
|
+
cancel(error);
|
|
496
|
+
}
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
/** Installs the DOM-backed half of the draft Declarative WebMCP API. */
|
|
500
|
+
function installDeclarativeForms(document, context) {
|
|
501
|
+
let active = true;
|
|
502
|
+
const registrations = /* @__PURE__ */ new Map();
|
|
503
|
+
const blockedDefinitions = /* @__PURE__ */ new Map();
|
|
504
|
+
const observers = /* @__PURE__ */ new Map();
|
|
505
|
+
const onSubmit = (event) => {
|
|
506
|
+
if (!(event instanceof SubmitEvent) || !event.isTrusted || !(event.target instanceof HTMLFormElement)) return;
|
|
507
|
+
const submission = activeSubmissions.get(event.target);
|
|
508
|
+
if (!submission) return;
|
|
509
|
+
agentInvokedEvents.add(event);
|
|
510
|
+
submission.complete(event);
|
|
511
|
+
};
|
|
512
|
+
const onReset = (event) => {
|
|
513
|
+
if (!event.isTrusted || !(event.target instanceof HTMLFormElement)) return;
|
|
514
|
+
const form = event.target;
|
|
515
|
+
queueMicrotask(() => {
|
|
516
|
+
if (event.defaultPrevented) return;
|
|
517
|
+
registrations.get(form)?.cancelPending?.(new DOMException("Tool execution cancelled by form reset", "UnknownError"));
|
|
518
|
+
});
|
|
519
|
+
};
|
|
520
|
+
function stopObservingRoot(root) {
|
|
521
|
+
observers.get(root)?.disconnect();
|
|
522
|
+
observers.delete(root);
|
|
523
|
+
root.removeEventListener("reset", onReset, true);
|
|
524
|
+
root.removeEventListener("submit", onSubmit, true);
|
|
525
|
+
}
|
|
526
|
+
function observeRoot(root) {
|
|
527
|
+
if (observers.has(root)) return;
|
|
528
|
+
const observer = new MutationObserver(sync);
|
|
529
|
+
observers.set(root, observer);
|
|
530
|
+
observer.observe(root, {
|
|
531
|
+
attributes: true,
|
|
532
|
+
characterData: true,
|
|
533
|
+
childList: true,
|
|
534
|
+
subtree: true
|
|
535
|
+
});
|
|
536
|
+
root.addEventListener("reset", onReset, true);
|
|
537
|
+
root.addEventListener("submit", onSubmit, true);
|
|
538
|
+
}
|
|
539
|
+
function sync() {
|
|
540
|
+
observeRoot(document);
|
|
541
|
+
for (const root of observers.keys()) if (root instanceof ShadowRoot && !Reflect.get(Node.prototype, "isConnected", root.host)) stopObservingRoot(root);
|
|
542
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
543
|
+
const selected = /* @__PURE__ */ new Map();
|
|
544
|
+
const selectedByName = /* @__PURE__ */ new Map();
|
|
545
|
+
for (const root of observers.keys()) for (const element of root.querySelectorAll("*")) {
|
|
546
|
+
const shadowRoot = getOpenShadowRoot(element);
|
|
547
|
+
if (shadowRoot) observeRoot(shadowRoot);
|
|
548
|
+
if (!(element instanceof HTMLFormElement) || !formHasAttribute(element, "toolname") || !formHasAttribute(element, "tooldescription") || !isFormConnected(element)) continue;
|
|
549
|
+
const form = element;
|
|
550
|
+
candidates.add(form);
|
|
551
|
+
const definition = toolDefinition(form);
|
|
552
|
+
const fingerprint = JSON.stringify(definition);
|
|
553
|
+
const blockedFingerprint = blockedDefinitions.get(form);
|
|
554
|
+
if (blockedFingerprint === fingerprint) continue;
|
|
555
|
+
const retryingChangedDefinition = blockedFingerprint !== void 0;
|
|
556
|
+
blockedDefinitions.delete(form);
|
|
557
|
+
const existingForm = selectedByName.get(definition.name);
|
|
558
|
+
if (existingForm && !retryingChangedDefinition) {
|
|
559
|
+
blockedDefinitions.set(form, fingerprint);
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
if (existingForm) {
|
|
563
|
+
const existingSelection = selected.get(existingForm);
|
|
564
|
+
if (existingSelection) {
|
|
565
|
+
blockedDefinitions.set(existingForm, existingSelection.fingerprint);
|
|
566
|
+
selected.delete(existingForm);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
selectedByName.set(definition.name, form);
|
|
570
|
+
selected.set(form, {
|
|
571
|
+
definition,
|
|
572
|
+
fingerprint
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
for (const form of blockedDefinitions.keys()) if (!candidates.has(form)) blockedDefinitions.delete(form);
|
|
576
|
+
for (const [form, registration] of registrations) if (selected.get(form)?.fingerprint !== registration.fingerprint) {
|
|
577
|
+
registration.cancelPending?.(new DOMException("Tool execution cancelled because its form changed", "UnknownError"));
|
|
578
|
+
registration.controller.abort();
|
|
579
|
+
registrations.delete(form);
|
|
580
|
+
}
|
|
581
|
+
for (const [form, { definition, fingerprint }] of selected) {
|
|
582
|
+
if (registrations.has(form)) continue;
|
|
583
|
+
const controller = new AbortController();
|
|
584
|
+
const registration = {
|
|
585
|
+
controller,
|
|
586
|
+
fingerprint,
|
|
587
|
+
form
|
|
588
|
+
};
|
|
589
|
+
registrations.set(form, registration);
|
|
590
|
+
context.registerTool({
|
|
591
|
+
name: definition.name,
|
|
592
|
+
title: definition.title,
|
|
593
|
+
description: definition.description,
|
|
594
|
+
inputSchema: definition.inputSchema,
|
|
595
|
+
execute(input) {
|
|
596
|
+
const submitter = findSubmitter(form);
|
|
597
|
+
if (!definition.autosubmit && !submitter) throw new DOMException("A declarative form without toolautosubmit requires a submit button", "UnknownError");
|
|
598
|
+
fillForm(form, input);
|
|
599
|
+
return waitForSubmission(registration, definition.name, definition.autosubmit, submitter);
|
|
600
|
+
}
|
|
601
|
+
}, { signal: controller.signal }).catch((error) => {
|
|
602
|
+
controller.abort();
|
|
603
|
+
if (error?.name === "AbortError") return;
|
|
604
|
+
console.error(`[webmcp] declarative form tool "${definition.name}" was not registered:`, error);
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
let restoreAttachShadow = () => {};
|
|
609
|
+
const elementPrototype = document.defaultView?.Element.prototype;
|
|
610
|
+
const attachShadowDescriptor = elementPrototype ? Object.getOwnPropertyDescriptor(elementPrototype, "attachShadow") : void 0;
|
|
611
|
+
if (elementPrototype && attachShadowDescriptor?.configurable) {
|
|
612
|
+
const nativeAttachShadow = elementPrototype.attachShadow;
|
|
613
|
+
const attachShadow = function(init) {
|
|
614
|
+
const root = nativeAttachShadow.call(this, init);
|
|
615
|
+
if (active && root.mode === "open" && Reflect.get(Node.prototype, "ownerDocument", this) === document && Reflect.get(Node.prototype, "isConnected", this)) observeRoot(root);
|
|
616
|
+
return root;
|
|
617
|
+
};
|
|
618
|
+
Object.defineProperty(elementPrototype, "attachShadow", {
|
|
619
|
+
...attachShadowDescriptor,
|
|
620
|
+
value: attachShadow
|
|
621
|
+
});
|
|
622
|
+
restoreAttachShadow = () => {
|
|
623
|
+
if (elementPrototype.attachShadow === attachShadow) Object.defineProperty(elementPrototype, "attachShadow", attachShadowDescriptor);
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
let restoreFormSubmit = () => {};
|
|
627
|
+
const formPrototype = document.defaultView?.HTMLFormElement.prototype;
|
|
628
|
+
const submitDescriptor = formPrototype ? Object.getOwnPropertyDescriptor(formPrototype, "submit") : void 0;
|
|
629
|
+
if (formPrototype && submitDescriptor?.configurable) {
|
|
630
|
+
const nativeSubmit = formPrototype.submit;
|
|
631
|
+
const submit = function() {
|
|
632
|
+
nativeSubmit.call(this);
|
|
633
|
+
if (active) activeSubmissions.get(this)?.direct();
|
|
634
|
+
};
|
|
635
|
+
Object.defineProperty(formPrototype, "submit", {
|
|
636
|
+
...submitDescriptor,
|
|
637
|
+
value: submit
|
|
638
|
+
});
|
|
639
|
+
restoreFormSubmit = () => {
|
|
640
|
+
if (formPrototype.submit === submit) Object.defineProperty(formPrototype, "submit", submitDescriptor);
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
sync();
|
|
644
|
+
return () => {
|
|
645
|
+
active = false;
|
|
646
|
+
restoreFormSubmit();
|
|
647
|
+
restoreAttachShadow();
|
|
648
|
+
for (const root of observers.keys()) stopObservingRoot(root);
|
|
649
|
+
blockedDefinitions.clear();
|
|
650
|
+
for (const registration of registrations.values()) {
|
|
651
|
+
registration.cancelPending?.(new DOMException("Tool execution cancelled", "UnknownError"));
|
|
652
|
+
registration.controller.abort();
|
|
653
|
+
}
|
|
654
|
+
registrations.clear();
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
//#endregion
|
|
3
659
|
//#region src/index.ts
|
|
4
|
-
const FAILED_TO_PARSE_INPUT_ARGUMENTS_MESSAGE = "Failed to parse input arguments";
|
|
5
|
-
const TOOL_INVOCATION_FAILED_MESSAGE = "Tool was executed but the invocation failed. For example, the script function threw an error";
|
|
6
|
-
const TOOL_CANCELLED_MESSAGE = "Tool was cancelled";
|
|
7
|
-
/** WebMCP §4.2 tool name: ASCII alnum, underscore, hyphen, period; 1–128 code points. */
|
|
8
|
-
const VALID_TOOL_NAME_RE = /^[A-Za-z0-9_\-.]{1,128}$/u;
|
|
9
660
|
const POLYFILL_MARKER_PROPERTY = "__isWebMCPPolyfill";
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
661
|
+
const REGISTERED_INPUT_SCHEMA_SYMBOL = Symbol("registeredInputSchema");
|
|
662
|
+
const REGISTRATION_SIGNAL_SYMBOL = Symbol("registrationSignal");
|
|
663
|
+
const REGISTRATION_ABORT_SYMBOL = Symbol("registrationAbort");
|
|
664
|
+
const installedProperties = [];
|
|
665
|
+
let installedContext = null;
|
|
666
|
+
let cleanupDeclarativeForms = null;
|
|
667
|
+
function currentOrigin() {
|
|
668
|
+
return typeof globalThis.origin === "string" ? globalThis.origin : globalThis.location?.origin ?? "";
|
|
669
|
+
}
|
|
670
|
+
function installProperty(target, key, descriptor) {
|
|
671
|
+
const previous = Object.getOwnPropertyDescriptor(target, key);
|
|
672
|
+
try {
|
|
673
|
+
Object.defineProperty(target, key, descriptor);
|
|
674
|
+
} catch (error) {
|
|
675
|
+
cleanupWebMCPPolyfill();
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
installedProperties.push({
|
|
679
|
+
target,
|
|
680
|
+
key,
|
|
681
|
+
previous
|
|
682
|
+
});
|
|
683
|
+
}
|
|
23
684
|
var StrictWebMCPContext = class extends EventTarget {
|
|
24
|
-
tools = /* @__PURE__ */ new Map();
|
|
25
|
-
testingShim = null;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
685
|
+
#tools = /* @__PURE__ */ new Map();
|
|
686
|
+
#testingShim = null;
|
|
687
|
+
#ontoolchangeHandler = null;
|
|
688
|
+
#ontoolchangeListener = (event) => {
|
|
689
|
+
this.#ontoolchangeHandler?.call(this, event);
|
|
690
|
+
};
|
|
691
|
+
#domException;
|
|
692
|
+
#ownerDocument;
|
|
693
|
+
constructor(ownerDocument) {
|
|
694
|
+
super();
|
|
695
|
+
this.#ownerDocument = ownerDocument;
|
|
696
|
+
this.#domException = ownerDocument?.defaultView?.DOMException ?? DOMException;
|
|
697
|
+
Object.defineProperty(this, POLYFILL_MARKER_PROPERTY, {
|
|
698
|
+
value: true,
|
|
699
|
+
enumerable: false,
|
|
700
|
+
writable: false,
|
|
701
|
+
configurable: false
|
|
702
|
+
});
|
|
703
|
+
}
|
|
29
704
|
get ontoolchange() {
|
|
30
|
-
return this
|
|
705
|
+
return this.#ontoolchangeHandler;
|
|
31
706
|
}
|
|
32
707
|
set ontoolchange(handler) {
|
|
33
|
-
|
|
708
|
+
const listener = typeof handler === "function" ? handler : null;
|
|
709
|
+
if (listener === null) {
|
|
710
|
+
this.#ontoolchangeHandler = null;
|
|
711
|
+
super.removeEventListener("toolchange", this.#ontoolchangeListener);
|
|
712
|
+
return;
|
|
713
|
+
}
|
|
714
|
+
if (this.#ontoolchangeHandler === null) super.addEventListener("toolchange", this.#ontoolchangeListener);
|
|
715
|
+
this.#ontoolchangeHandler = listener;
|
|
34
716
|
}
|
|
35
|
-
registerTool(tool, options) {
|
|
717
|
+
async registerTool(tool, options = {}) {
|
|
718
|
+
validateWebMcpAccess(this.#ownerDocument);
|
|
36
719
|
const signal = options?.signal;
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
if (
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
executeTool(tool, inputArgsJson, options) {
|
|
55
|
-
return this.executeToolByName(tool.name, inputArgsJson, options, false);
|
|
56
|
-
}
|
|
57
|
-
getTestingShim() {
|
|
58
|
-
if (!this.testingShim) this.testingShim = new PolyfillTestingShim(this);
|
|
59
|
-
return this.testingShim;
|
|
720
|
+
const normalized = normalizeToolDescriptor(tool, this.#tools);
|
|
721
|
+
signal?.throwIfAborted();
|
|
722
|
+
validatePotentiallyTrustworthyOrigins(options?.exposedTo);
|
|
723
|
+
signal?.throwIfAborted();
|
|
724
|
+
if (options?.exposedTo?.length) throw new this.#domException("Cross-document tool exposure requires native WebMCP", "NotSupportedError");
|
|
725
|
+
this.#tools.set(normalized.name, normalized);
|
|
726
|
+
if (signal) {
|
|
727
|
+
const abort = () => {
|
|
728
|
+
if (this.#removeTool(normalized.name, normalized)) this.#notifyToolsChanged();
|
|
729
|
+
};
|
|
730
|
+
normalized[REGISTRATION_SIGNAL_SYMBOL] = signal;
|
|
731
|
+
normalized[REGISTRATION_ABORT_SYMBOL] = abort;
|
|
732
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
733
|
+
}
|
|
734
|
+
await this.#notifyToolsChanged();
|
|
735
|
+
if (signal?.aborted) throw signal.reason;
|
|
60
736
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
inputSchema = "{\"type\":\"object\"}";
|
|
69
|
-
}
|
|
737
|
+
async getTools(options = {}) {
|
|
738
|
+
validateWebMcpAccess(this.#ownerDocument);
|
|
739
|
+
validatePotentiallyTrustworthyOrigins(options?.fromOrigins);
|
|
740
|
+
if (options?.fromOrigins?.length) throw new this.#domException("Cross-document tool discovery requires native WebMCP", "NotSupportedError");
|
|
741
|
+
const tools = [...this.#tools.values()].map((tool) => {
|
|
742
|
+
const serialized = tool[REGISTERED_INPUT_SCHEMA_SYMBOL];
|
|
743
|
+
const parsed = serialized === void 0 ? void 0 : JSON.parse(serialized);
|
|
70
744
|
return {
|
|
71
745
|
name: tool.name,
|
|
746
|
+
title: tool.title ?? "",
|
|
72
747
|
description: tool.description,
|
|
73
|
-
inputSchema
|
|
748
|
+
...isPlainObject(parsed) ? { inputSchema: parsed } : {},
|
|
749
|
+
origin: currentOrigin(),
|
|
750
|
+
window: globalThis.window,
|
|
751
|
+
...tool.annotations ? { annotations: toWebMcpAnnotations(tool.annotations) } : {}
|
|
74
752
|
};
|
|
75
|
-
});
|
|
753
|
+
}).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
754
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
755
|
+
return tools;
|
|
76
756
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
});
|
|
757
|
+
async executeTool(tool, inputArgsJson, options = {}) {
|
|
758
|
+
validateWebMcpAccess(this.#ownerDocument);
|
|
759
|
+
if (tool === null || typeof tool !== "object") throw new TypeError("RegisteredTool must be an object");
|
|
760
|
+
for (const required of [
|
|
761
|
+
"name",
|
|
762
|
+
"description",
|
|
763
|
+
"window",
|
|
764
|
+
"origin"
|
|
765
|
+
]) if (!(required in tool)) throw new TypeError(`RegisteredTool.${required} is required`);
|
|
766
|
+
validateExecutableOrigin(tool.origin);
|
|
767
|
+
if (tool.window !== globalThis.window || tool.origin !== currentOrigin()) throw createUnknownError(`Tool not found: ${tool.name}`);
|
|
768
|
+
return this.#invokeToolByName(tool.name, inputArgsJson, options);
|
|
769
|
+
}
|
|
770
|
+
/** @internal Used by initializeWebMCPPolyfill */
|
|
771
|
+
static getTestingShim(context) {
|
|
772
|
+
context.#testingShim ??= new PolyfillTestingShim(context);
|
|
773
|
+
return context.#testingShim;
|
|
88
774
|
}
|
|
89
775
|
/** @internal Used by PolyfillTestingShim */
|
|
90
|
-
|
|
91
|
-
return
|
|
776
|
+
static getToolInfos(context) {
|
|
777
|
+
return [...context.#tools.values()].map((tool) => ({
|
|
778
|
+
name: tool.name,
|
|
779
|
+
description: tool.description,
|
|
780
|
+
...tool[REGISTERED_INPUT_SCHEMA_SYMBOL] === void 0 ? {} : { inputSchema: tool[REGISTERED_INPUT_SCHEMA_SYMBOL] }
|
|
781
|
+
}));
|
|
92
782
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
783
|
+
/** @internal Used by PolyfillTestingShim */
|
|
784
|
+
static executeToolForTesting(context, toolName, inputArgsJson, options) {
|
|
785
|
+
validateWebMcpAccess(context.#ownerDocument);
|
|
786
|
+
return context.#invokeToolByName(toolName, inputArgsJson, options);
|
|
787
|
+
}
|
|
788
|
+
/** @internal Used by cleanupWebMCPPolyfill */
|
|
789
|
+
static dispose(context) {
|
|
790
|
+
for (const name of context.#tools.keys()) context.#removeTool(name);
|
|
791
|
+
context.ontoolchange = null;
|
|
792
|
+
context.#testingShim?.dispose();
|
|
793
|
+
}
|
|
794
|
+
#removeTool(name, expected) {
|
|
795
|
+
const registered = this.#tools.get(name);
|
|
796
|
+
if (!registered || expected && registered !== expected) return false;
|
|
797
|
+
const signal = registered[REGISTRATION_SIGNAL_SYMBOL];
|
|
798
|
+
const abort = registered[REGISTRATION_ABORT_SYMBOL];
|
|
799
|
+
if (signal && abort) signal.removeEventListener("abort", abort);
|
|
800
|
+
return this.#tools.delete(name);
|
|
801
|
+
}
|
|
802
|
+
async #invokeToolByName(toolName, inputArgsJson, options) {
|
|
803
|
+
options?.signal?.throwIfAborted();
|
|
804
|
+
const tool = this.#tools.get(toolName);
|
|
96
805
|
if (!tool) throw createUnknownError(`Tool not found: ${toolName}`);
|
|
97
|
-
const args =
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
let
|
|
101
|
-
const client = { requestUserInteraction: async (callback) => {
|
|
102
|
-
if (!contextActive) throw new Error(`ModelContextClient for tool "${toolName}" is no longer active after execute() resolved`);
|
|
103
|
-
if (typeof callback !== "function") throw new TypeError("requestUserInteraction(callback) requires a function callback");
|
|
104
|
-
return callback();
|
|
105
|
-
} };
|
|
806
|
+
const args = parseChromeToolInput(inputArgsJson);
|
|
807
|
+
options?.signal?.throwIfAborted();
|
|
808
|
+
if (tool[REGISTRATION_SIGNAL_SYMBOL]?.aborted) throw createUnknownError("Tool unregistered");
|
|
809
|
+
let rawResult;
|
|
106
810
|
try {
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
const normalizedResult = normalizeToolResponse(rawResult);
|
|
110
|
-
const outputValidationError = validateOutputForTool(normalizedResult, tool);
|
|
111
|
-
if (outputValidationError) throw new Error(outputValidationError);
|
|
112
|
-
if (normalizeResult) return toSerializedTestingResult(normalizedResult);
|
|
113
|
-
const serialized = JSON.stringify(rawResult);
|
|
114
|
-
return serialized === void 0 ? null : serialized;
|
|
811
|
+
const registrationSignal = tool[REGISTRATION_SIGNAL_SYMBOL];
|
|
812
|
+
rawResult = await withAbortSignal(withAbortSignal(Promise.resolve(tool.execute(args)), registrationSignal, () => createUnknownError("Tool unregistered")), options?.signal);
|
|
115
813
|
} catch (error) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
814
|
+
if (options?.signal?.aborted && error === options.signal.reason) throw error;
|
|
815
|
+
if (tool[REGISTRATION_SIGNAL_SYMBOL]?.aborted) throw error;
|
|
816
|
+
throw createToolInvocationFailedError(error);
|
|
119
817
|
}
|
|
818
|
+
return serializeChromeToolResult(rawResult);
|
|
120
819
|
}
|
|
121
|
-
notifyToolsChanged() {
|
|
122
|
-
|
|
123
|
-
this.
|
|
124
|
-
|
|
125
|
-
this.toolsChangedQueued = false;
|
|
126
|
-
const event = new Event("toolchange");
|
|
127
|
-
try {
|
|
128
|
-
this._ontoolchange?.call(this, event);
|
|
129
|
-
} catch (error) {
|
|
130
|
-
console.warn("[WebMCPPolyfill] navigator.modelContext.ontoolchange handler threw:", error);
|
|
131
|
-
}
|
|
132
|
-
this.dispatchEvent(event);
|
|
133
|
-
this.testingShim?.dispatchToolChange();
|
|
134
|
-
});
|
|
135
|
-
}
|
|
136
|
-
warnUnregisterToolDeprecationOnce() {
|
|
137
|
-
if (this.unregisterToolDeprecationWarned) return;
|
|
138
|
-
this.unregisterToolDeprecationWarned = true;
|
|
139
|
-
console.warn("[WebMCPPolyfill] navigator.modelContext.unregisterTool() is deprecated. The April 23, 2026 WebMCP draft removed it in favor of registerTool(tool, { signal }) — pass an AbortSignal and abort it to unregister.");
|
|
820
|
+
async #notifyToolsChanged() {
|
|
821
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
822
|
+
this.dispatchEvent(new Event("toolchange"));
|
|
823
|
+
this.#testingShim?.dispatchToolChange();
|
|
140
824
|
}
|
|
141
825
|
};
|
|
826
|
+
const modelContextConstructor = function ModelContext() {
|
|
827
|
+
throw new TypeError("Illegal constructor");
|
|
828
|
+
};
|
|
829
|
+
Object.defineProperty(modelContextConstructor, "name", { value: "ModelContext" });
|
|
830
|
+
Object.defineProperty(modelContextConstructor, "prototype", {
|
|
831
|
+
value: StrictWebMCPContext.prototype,
|
|
832
|
+
writable: false
|
|
833
|
+
});
|
|
834
|
+
Object.setPrototypeOf(modelContextConstructor, EventTarget);
|
|
835
|
+
Object.defineProperty(StrictWebMCPContext.prototype, "constructor", {
|
|
836
|
+
configurable: true,
|
|
837
|
+
writable: true,
|
|
838
|
+
value: modelContextConstructor
|
|
839
|
+
});
|
|
840
|
+
Object.defineProperty(StrictWebMCPContext.prototype, Symbol.toStringTag, {
|
|
841
|
+
configurable: true,
|
|
842
|
+
value: "ModelContext"
|
|
843
|
+
});
|
|
844
|
+
for (const member of [
|
|
845
|
+
"registerTool",
|
|
846
|
+
"getTools",
|
|
847
|
+
"executeTool",
|
|
848
|
+
"ontoolchange"
|
|
849
|
+
]) {
|
|
850
|
+
const descriptor = Object.getOwnPropertyDescriptor(StrictWebMCPContext.prototype, member);
|
|
851
|
+
if (descriptor) Object.defineProperty(StrictWebMCPContext.prototype, member, {
|
|
852
|
+
...descriptor,
|
|
853
|
+
enumerable: true
|
|
854
|
+
});
|
|
855
|
+
}
|
|
142
856
|
/**
|
|
143
857
|
* EventTarget-based testing shim matching the native Chromium ModelContextTesting surface.
|
|
144
858
|
*
|
|
145
|
-
* Fires `toolchange` events and supports the `ontoolchange` handler property
|
|
146
|
-
* matching the native Chromium 148 API. The deprecated `registerToolsChangedCallback`
|
|
147
|
-
* is kept as a compat layer that wraps `addEventListener`.
|
|
859
|
+
* Fires `toolchange` events and supports the `ontoolchange` handler property.
|
|
148
860
|
*/
|
|
149
861
|
var PolyfillTestingShim = class extends EventTarget {
|
|
150
|
-
context;
|
|
151
862
|
_ontoolchange = null;
|
|
863
|
+
ontoolchangeListener = (event) => {
|
|
864
|
+
this._ontoolchange?.call(this, event);
|
|
865
|
+
};
|
|
866
|
+
#context;
|
|
152
867
|
constructor(context) {
|
|
153
868
|
super();
|
|
154
|
-
this
|
|
869
|
+
this.#context = context;
|
|
155
870
|
}
|
|
156
871
|
listTools() {
|
|
157
|
-
return
|
|
872
|
+
return StrictWebMCPContext.getToolInfos(this.#context);
|
|
158
873
|
}
|
|
159
874
|
executeTool(toolName, inputArgsJson, options) {
|
|
160
|
-
return
|
|
161
|
-
}
|
|
162
|
-
getCrossDocumentScriptToolResult() {
|
|
163
|
-
return Promise.resolve("[]");
|
|
875
|
+
return StrictWebMCPContext.executeToolForTesting(this.#context, toolName, inputArgsJson, options);
|
|
164
876
|
}
|
|
165
877
|
get ontoolchange() {
|
|
166
878
|
return this._ontoolchange;
|
|
167
879
|
}
|
|
168
880
|
set ontoolchange(handler) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
this.addEventListener("toolchange", callback);
|
|
881
|
+
const listener = typeof handler === "function" ? handler : null;
|
|
882
|
+
if (listener === null) {
|
|
883
|
+
this._ontoolchange = null;
|
|
884
|
+
super.removeEventListener("toolchange", this.ontoolchangeListener);
|
|
885
|
+
return;
|
|
886
|
+
}
|
|
887
|
+
if (this._ontoolchange === null) super.addEventListener("toolchange", this.ontoolchangeListener);
|
|
888
|
+
this._ontoolchange = listener;
|
|
178
889
|
}
|
|
179
890
|
/** @internal Called by StrictWebMCPContext when tools change. */
|
|
180
891
|
dispatchToolChange() {
|
|
181
|
-
|
|
182
|
-
try {
|
|
183
|
-
this._ontoolchange?.call(this, event);
|
|
184
|
-
} catch (error) {
|
|
185
|
-
console.warn("[WebMCPPolyfill] ontoolchange handler threw:", error);
|
|
186
|
-
}
|
|
187
|
-
this.dispatchEvent(event);
|
|
188
|
-
this.dispatchEvent(new Event("toolschanged"));
|
|
892
|
+
this.dispatchEvent(new Event("toolchange"));
|
|
189
893
|
}
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
try {
|
|
193
|
-
return new DOMException(message, "UnknownError");
|
|
194
|
-
} catch {
|
|
195
|
-
const error = new Error(message);
|
|
196
|
-
error.name = "UnknownError";
|
|
197
|
-
return error;
|
|
894
|
+
dispose() {
|
|
895
|
+
this.ontoolchange = null;
|
|
198
896
|
}
|
|
199
|
-
}
|
|
200
|
-
function createInvalidStateError(message) {
|
|
201
|
-
try {
|
|
202
|
-
return new DOMException(message, "InvalidStateError");
|
|
203
|
-
} catch {
|
|
204
|
-
const error = new Error(message);
|
|
205
|
-
error.name = "InvalidStateError";
|
|
206
|
-
return error;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
function parseInputArgsJson(inputArgsJson) {
|
|
210
|
-
let parsed;
|
|
211
|
-
try {
|
|
212
|
-
parsed = JSON.parse(inputArgsJson);
|
|
213
|
-
} catch {
|
|
214
|
-
throw createUnknownError(FAILED_TO_PARSE_INPUT_ARGUMENTS_MESSAGE);
|
|
215
|
-
}
|
|
216
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw createUnknownError(FAILED_TO_PARSE_INPUT_ARGUMENTS_MESSAGE);
|
|
217
|
-
return parsed;
|
|
218
|
-
}
|
|
219
|
-
function getToolNameForUnregister(nameOrTool) {
|
|
220
|
-
if (typeof nameOrTool === "string") return nameOrTool;
|
|
221
|
-
if (isPlainObject(nameOrTool) && typeof nameOrTool.name === "string") return nameOrTool.name;
|
|
222
|
-
throw new TypeError("Failed to execute 'unregisterTool' on 'ModelContext': parameter 1 must be a string or an object with a string name.");
|
|
223
|
-
}
|
|
897
|
+
};
|
|
224
898
|
function normalizeToolDescriptor(tool, existing) {
|
|
225
899
|
if (!tool || typeof tool !== "object") throw new TypeError("registerTool(tool) requires a tool object");
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
if (existing.has(tool.name)) throw new Error(`Tool already registered: ${tool.name}`);
|
|
231
|
-
const normalizedInputSchema = normalizeInputSchema(tool.inputSchema);
|
|
232
|
-
const annotations = tool.annotations;
|
|
233
|
-
const normalizedAnnotations = annotations ? {
|
|
234
|
-
...annotations,
|
|
235
|
-
...annotations.readOnlyHint === "true" ? { readOnlyHint: true } : annotations.readOnlyHint === "false" ? { readOnlyHint: false } : {},
|
|
236
|
-
...annotations.destructiveHint === "true" ? { destructiveHint: true } : annotations.destructiveHint === "false" ? { destructiveHint: false } : {},
|
|
237
|
-
...annotations.idempotentHint === "true" ? { idempotentHint: true } : annotations.idempotentHint === "false" ? { idempotentHint: false } : {},
|
|
238
|
-
...annotations.openWorldHint === "true" ? { openWorldHint: true } : annotations.openWorldHint === "false" ? { openWorldHint: false } : {}
|
|
239
|
-
} : void 0;
|
|
240
|
-
return {
|
|
241
|
-
...tool,
|
|
242
|
-
...normalizedAnnotations ? { annotations: normalizedAnnotations } : {},
|
|
243
|
-
inputSchema: normalizedInputSchema.inputSchema,
|
|
244
|
-
[STANDARD_VALIDATOR_SYMBOL]: normalizedInputSchema.standardValidator
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
function formatStandardIssuePath(path) {
|
|
248
|
-
if (!path || path.length === 0) return null;
|
|
249
|
-
const segments = path.map((segment) => {
|
|
250
|
-
if (isPlainObject(segment) && "key" in segment) return segment.key;
|
|
251
|
-
return segment;
|
|
252
|
-
}).map((segment) => String(segment)).filter((segment) => segment.length > 0);
|
|
253
|
-
if (segments.length === 0) return null;
|
|
254
|
-
return segments.join(".");
|
|
255
|
-
}
|
|
256
|
-
async function validateArgsWithStandardSchema(args, schema) {
|
|
257
|
-
let result;
|
|
258
|
-
try {
|
|
259
|
-
result = await Promise.resolve(schema["~standard"].validate(args));
|
|
260
|
-
} catch (error) {
|
|
261
|
-
const detail = error instanceof Error ? `: ${error.message}` : "";
|
|
262
|
-
console.error("[WebMCPPolyfill] Standard Schema validation threw unexpectedly:", error);
|
|
263
|
-
return `Input validation error: schema validation failed${detail}`;
|
|
264
|
-
}
|
|
265
|
-
if (!result.issues || result.issues.length === 0) return null;
|
|
266
|
-
const firstIssue = result.issues[0];
|
|
267
|
-
if (!firstIssue) return "Input validation error";
|
|
268
|
-
const path = formatStandardIssuePath(firstIssue?.path);
|
|
269
|
-
if (path) return `Input validation error: ${firstIssue.message} at ${path}`;
|
|
270
|
-
return `Input validation error: ${firstIssue.message}`;
|
|
271
|
-
}
|
|
272
|
-
async function validateArgsForTool(args, tool) {
|
|
273
|
-
return validateArgsWithStandardSchema(args, tool[STANDARD_VALIDATOR_SYMBOL]);
|
|
274
|
-
}
|
|
275
|
-
function validateOutputForTool(result, tool) {
|
|
276
|
-
if (!tool.outputSchema || result.isError) return null;
|
|
277
|
-
if (result.structuredContent === void 0) return `Output validation error: Tool ${tool.name} has an output schema but no structured content was provided`;
|
|
278
|
-
const issue = validateValueWithSchema(result.structuredContent, tool.outputSchema);
|
|
279
|
-
return issue ? `Output validation error: ${issue.message}` : null;
|
|
280
|
-
}
|
|
281
|
-
function isCallToolResult(value) {
|
|
282
|
-
return isPlainObject(value) && Array.isArray(value.content);
|
|
283
|
-
}
|
|
284
|
-
function serializeTextContent(value) {
|
|
285
|
-
if (typeof value === "string") return value;
|
|
286
|
-
try {
|
|
287
|
-
return JSON.stringify(value) ?? String(value);
|
|
288
|
-
} catch {
|
|
289
|
-
return String(value);
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
function normalizeToolResponse(value) {
|
|
293
|
-
if (isCallToolResult(value)) return value;
|
|
294
|
-
const structuredContent = toJsonValue(value);
|
|
900
|
+
const coerced = coerceWebMcpToolDescriptor(tool);
|
|
901
|
+
validateWebMcpToolDescriptor(coerced);
|
|
902
|
+
if (existing.has(coerced.name)) throw createInvalidStateError(`Tool already registered: ${coerced.name}`);
|
|
903
|
+
const registeredInputSchema = coerced.inputSchema === void 0 ? void 0 : serializeInputSchema(coerced.inputSchema);
|
|
295
904
|
return {
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
}
|
|
300
|
-
...
|
|
301
|
-
|
|
905
|
+
name: coerced.name,
|
|
906
|
+
...coerced.title === void 0 ? {} : { title: coerced.title },
|
|
907
|
+
description: coerced.description,
|
|
908
|
+
...coerced.inputSchema === void 0 ? {} : { inputSchema: coerced.inputSchema },
|
|
909
|
+
...coerced.annotations === void 0 ? {} : { annotations: coerced.annotations },
|
|
910
|
+
execute: (input) => Reflect.apply(coerced.execute, void 0, [input]),
|
|
911
|
+
...registeredInputSchema !== void 0 ? { [REGISTERED_INPUT_SCHEMA_SYMBOL]: registeredInputSchema } : {}
|
|
302
912
|
};
|
|
303
913
|
}
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
reject(createUnknownError(TOOL_CANCELLED_MESSAGE));
|
|
325
|
-
};
|
|
326
|
-
const cleanup = () => {
|
|
327
|
-
signal.removeEventListener("abort", onAbort);
|
|
328
|
-
};
|
|
329
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
330
|
-
operation.then((value) => {
|
|
331
|
-
cleanup();
|
|
332
|
-
resolve(value);
|
|
333
|
-
}, (error) => {
|
|
334
|
-
cleanup();
|
|
335
|
-
reject(error);
|
|
336
|
-
});
|
|
337
|
-
});
|
|
338
|
-
}
|
|
339
|
-
function getNavigator() {
|
|
340
|
-
if (typeof navigator !== "undefined") return navigator;
|
|
341
|
-
return null;
|
|
342
|
-
}
|
|
343
|
-
function getDocument() {
|
|
344
|
-
if (typeof document !== "undefined") return document;
|
|
345
|
-
return null;
|
|
346
|
-
}
|
|
347
|
-
function defineNavigatorProperty(target, key, value) {
|
|
348
|
-
Object.defineProperty(target, key, {
|
|
349
|
-
configurable: true,
|
|
350
|
-
enumerable: true,
|
|
351
|
-
writable: false,
|
|
352
|
-
value
|
|
353
|
-
});
|
|
354
|
-
}
|
|
355
|
-
function defineDocumentModelContextProperty(target, value) {
|
|
356
|
-
Object.defineProperty(target, "modelContext", {
|
|
914
|
+
let navigatorModelContextDeprecationWarned = false;
|
|
915
|
+
/**
|
|
916
|
+
* Backing store for the `Document.prototype.modelContext` getter.
|
|
917
|
+
*
|
|
918
|
+
* WebIDL puts `[SameObject] readonly attribute ModelContext modelContext` on
|
|
919
|
+
* the Document *interface prototype object*, not on the document instance, and
|
|
920
|
+
* Chrome matches that. One shared accessor therefore needs somewhere to keep
|
|
921
|
+
* the per-document value, and keying it by document is what makes repeated
|
|
922
|
+
* reads return the identical object, as `[SameObject]` requires.
|
|
923
|
+
*/
|
|
924
|
+
const documentModelContexts = /* @__PURE__ */ new WeakMap();
|
|
925
|
+
const { modelContext: documentModelContextGetter } = { modelContext() {
|
|
926
|
+
if (!(this instanceof Document)) throw new TypeError("Illegal invocation");
|
|
927
|
+
return documentModelContexts.get(this);
|
|
928
|
+
} };
|
|
929
|
+
Object.defineProperty(documentModelContextGetter, "name", { value: "get modelContext" });
|
|
930
|
+
function defineDocumentModelContext(target, value) {
|
|
931
|
+
documentModelContexts.set(target, value);
|
|
932
|
+
if (Object.hasOwn(Document.prototype, "modelContext")) return;
|
|
933
|
+
installProperty(Document.prototype, "modelContext", {
|
|
357
934
|
configurable: true,
|
|
358
935
|
enumerable: true,
|
|
359
|
-
|
|
360
|
-
value
|
|
936
|
+
get: documentModelContextGetter
|
|
361
937
|
});
|
|
362
938
|
}
|
|
363
|
-
let navigatorModelContextDeprecationWarned = false;
|
|
364
939
|
function defineDeprecatedNavigatorModelContext(target, value) {
|
|
365
|
-
|
|
940
|
+
installProperty(target, "modelContext", {
|
|
366
941
|
configurable: true,
|
|
367
942
|
enumerable: true,
|
|
368
943
|
get() {
|
|
@@ -374,78 +949,70 @@ function defineDeprecatedNavigatorModelContext(target, value) {
|
|
|
374
949
|
}
|
|
375
950
|
});
|
|
376
951
|
}
|
|
952
|
+
function installSubmitEventPolyfill() {
|
|
953
|
+
const prototype = SubmitEvent.prototype;
|
|
954
|
+
if (!("agentInvoked" in prototype)) installProperty(prototype, "agentInvoked", {
|
|
955
|
+
configurable: true,
|
|
956
|
+
enumerable: true,
|
|
957
|
+
get() {
|
|
958
|
+
return isAgentInvokedSubmitEvent(this);
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
if (!("respondWith" in prototype)) installProperty(prototype, "respondWith", {
|
|
962
|
+
configurable: true,
|
|
963
|
+
enumerable: true,
|
|
964
|
+
writable: true,
|
|
965
|
+
value(agentResponse) {
|
|
966
|
+
respondWithAgentSubmitEvent(this, agentResponse);
|
|
967
|
+
}
|
|
968
|
+
});
|
|
969
|
+
}
|
|
377
970
|
function initializeWebMCPPolyfill(options) {
|
|
378
|
-
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
if (
|
|
383
|
-
const navigatorModelContext = nav
|
|
384
|
-
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
installState.previousDocumentModelContextDescriptor = Object.getOwnPropertyDescriptor(doc, "modelContext");
|
|
388
|
-
defineDocumentModelContextProperty(doc, navigatorModelContext);
|
|
389
|
-
installState.installedDocumentModelContext = true;
|
|
390
|
-
installState.installed = true;
|
|
971
|
+
if (globalThis.isSecureContext === false) return;
|
|
972
|
+
const nav = typeof navigator === "undefined" ? null : navigator;
|
|
973
|
+
const doc = typeof document === "undefined" ? null : document;
|
|
974
|
+
if (!nav || !doc || typeof window === "undefined") return;
|
|
975
|
+
if (doc.modelContext) return;
|
|
976
|
+
const navigatorModelContext = nav.modelContext;
|
|
977
|
+
if (installedProperties.length > 0) cleanupWebMCPPolyfill();
|
|
978
|
+
if (navigatorModelContext) {
|
|
979
|
+
defineDocumentModelContext(doc, navigatorModelContext);
|
|
391
980
|
return;
|
|
392
981
|
}
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
installState.installed = true;
|
|
982
|
+
const previousDescriptor = Object.getOwnPropertyDescriptor(window, "ModelContext");
|
|
983
|
+
if (previousDescriptor && !previousDescriptor.configurable) {
|
|
984
|
+
if (!Object.is(Reflect.get(window, "ModelContext"), modelContextConstructor)) throw new TypeError("Cannot install ModelContext over a non-configurable global");
|
|
985
|
+
} else installProperty(window, "ModelContext", {
|
|
986
|
+
configurable: true,
|
|
987
|
+
enumerable: false,
|
|
988
|
+
writable: true,
|
|
989
|
+
value: modelContextConstructor
|
|
990
|
+
});
|
|
991
|
+
const context = new StrictWebMCPContext(doc);
|
|
992
|
+
installedContext = context;
|
|
993
|
+
defineDocumentModelContext(doc, context);
|
|
994
|
+
navigatorModelContextDeprecationWarned = false;
|
|
995
|
+
defineDeprecatedNavigatorModelContext(nav, context);
|
|
996
|
+
installSubmitEventPolyfill();
|
|
997
|
+
if (options?.installTestingShim && !nav.modelContextTesting) installProperty(nav, "modelContextTesting", {
|
|
998
|
+
configurable: true,
|
|
999
|
+
enumerable: true,
|
|
1000
|
+
writable: false,
|
|
1001
|
+
value: StrictWebMCPContext.getTestingShim(context)
|
|
1002
|
+
});
|
|
1003
|
+
cleanupDeclarativeForms = installDeclarativeForms(doc, context);
|
|
416
1004
|
}
|
|
417
1005
|
function cleanupWebMCPPolyfill() {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
};
|
|
426
|
-
const nav = getNavigator();
|
|
427
|
-
const doc = getDocument();
|
|
428
|
-
if (doc && installState.installedDocumentModelContext) restore(doc, "modelContext", installState.previousDocumentModelContextDescriptor);
|
|
429
|
-
if (nav && installState.installedNavigatorModelContext) restore(nav, "modelContext", installState.previousNavigatorModelContextDescriptor);
|
|
430
|
-
if (nav && installState.installedNavigatorModelContextTesting) restore(nav, "modelContextTesting", installState.previousNavigatorModelContextTestingDescriptor);
|
|
431
|
-
installState.installed = false;
|
|
432
|
-
installState.previousDocumentModelContextDescriptor = void 0;
|
|
433
|
-
installState.previousNavigatorModelContextDescriptor = void 0;
|
|
434
|
-
installState.previousNavigatorModelContextTestingDescriptor = void 0;
|
|
435
|
-
installState.installedDocumentModelContext = false;
|
|
436
|
-
installState.installedNavigatorModelContext = false;
|
|
437
|
-
installState.installedNavigatorModelContextTesting = false;
|
|
1006
|
+
cleanupDeclarativeForms?.();
|
|
1007
|
+
cleanupDeclarativeForms = null;
|
|
1008
|
+
if (installedContext) StrictWebMCPContext.dispose(installedContext);
|
|
1009
|
+
installedContext = null;
|
|
1010
|
+
for (const { target, key, previous } of [...installedProperties].reverse()) if (previous) Object.defineProperty(target, key, previous);
|
|
1011
|
+
else Reflect.deleteProperty(target, key);
|
|
1012
|
+
installedProperties.length = 0;
|
|
438
1013
|
navigatorModelContextDeprecationWarned = false;
|
|
439
1014
|
}
|
|
440
|
-
if (typeof window !== "undefined" && typeof document !== "undefined") {
|
|
441
|
-
const options = window.__webMCPPolyfillOptions;
|
|
442
|
-
if (options?.autoInitialize !== false) try {
|
|
443
|
-
initializeWebMCPPolyfill(options);
|
|
444
|
-
} catch (error) {
|
|
445
|
-
console.error("[WebMCPPolyfill] Auto-initialization failed:", error);
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
1015
|
|
|
449
1016
|
//#endregion
|
|
450
|
-
export { cleanupWebMCPPolyfill, initializeWebMCPPolyfill
|
|
1017
|
+
export { cleanupWebMCPPolyfill, initializeWebMCPPolyfill };
|
|
451
1018
|
//# sourceMappingURL=index.js.map
|