@crestapps/webmcp 0.0.0-stage → 2.0.0-preview.246

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,269 @@
1
+ /*
2
+ ** NOTE: This file is generated by the assets build (npm run build) and should not be edited directly!
3
+ ** Edit its sources under Assets/ and rebuild; see Assets.json for the files it is built from.
4
+ */
5
+ (function() {
6
+ //#region src/Resources/CrestApps.AI.WebMcp.Resources/Assets/js/webmcp-page-tools.js
7
+ (function() {
8
+ "use strict";
9
+ var MAX_HEADINGS = 20;
10
+ var MAX_LINKS = 200;
11
+ var MAX_FIELDS = 200;
12
+ var MAX_VALUE_LENGTH = 2e3;
13
+ var selectors = {
14
+ description: "meta[name=\"description\"]",
15
+ headings: "h1, h2",
16
+ links: "nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]",
17
+ fields: "form input, form select, form textarea"
18
+ };
19
+ var SKIPPED_TYPES = [
20
+ "hidden",
21
+ "password",
22
+ "file",
23
+ "submit",
24
+ "button",
25
+ "reset",
26
+ "image"
27
+ ];
28
+ var SKIPPED_NAMES = ["__RequestVerificationToken"];
29
+ function cleanText(value) {
30
+ return (value == null ? "" : String(value)).replace(/\s+/g, " ").trim();
31
+ }
32
+ function resolveSameOrigin(href, loc) {
33
+ if (!href) return null;
34
+ try {
35
+ var url = new URL(href, loc.href);
36
+ return url.origin === loc.origin ? url : null;
37
+ } catch (error) {
38
+ return null;
39
+ }
40
+ }
41
+ function fieldType(field) {
42
+ var tag = (field.tagName || "").toLowerCase();
43
+ if (tag === "select" || tag === "textarea") return tag;
44
+ return (field.type || "text").toLowerCase();
45
+ }
46
+ function fieldLabel(field) {
47
+ if (field.labels && field.labels.length > 0) {
48
+ var label = cleanText(field.labels[0].textContent);
49
+ if (label) return label;
50
+ }
51
+ return cleanText(field.getAttribute("aria-label")) || cleanText(field.getAttribute("placeholder"));
52
+ }
53
+ function isListed(field) {
54
+ return !field.disabled && SKIPPED_TYPES.indexOf(fieldType(field)) < 0 && SKIPPED_NAMES.indexOf(field.name) < 0;
55
+ }
56
+ function listFields(doc) {
57
+ var fields = [];
58
+ var all = doc.querySelectorAll(selectors.fields);
59
+ for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) if (isListed(all[i])) fields.push(all[i]);
60
+ return fields;
61
+ }
62
+ function describeField(field) {
63
+ var type = fieldType(field);
64
+ var description = {
65
+ key: field.name || field.id,
66
+ name: field.name,
67
+ id: field.id,
68
+ label: fieldLabel(field),
69
+ type,
70
+ value: type === "checkbox" || type === "radio" ? !!field.checked : String(field.value == null ? "" : field.value).substring(0, MAX_VALUE_LENGTH),
71
+ required: !!field.required
72
+ };
73
+ if (type === "select") description.options = Array.prototype.map.call(field.options || [], function(option) {
74
+ return {
75
+ value: option.value,
76
+ text: cleanText(option.text),
77
+ selected: !!option.selected
78
+ };
79
+ });
80
+ return description;
81
+ }
82
+ function findField(doc, key) {
83
+ var wanted = cleanText(key).toLowerCase();
84
+ if (!wanted) return null;
85
+ var fields = listFields(doc);
86
+ var matchers = [
87
+ function(field) {
88
+ return (field.name || "").toLowerCase() === wanted;
89
+ },
90
+ function(field) {
91
+ return (field.id || "").toLowerCase() === wanted;
92
+ },
93
+ function(field) {
94
+ return fieldLabel(field).toLowerCase() === wanted;
95
+ }
96
+ ];
97
+ for (var m = 0; m < matchers.length; m++) for (var i = 0; i < fields.length; i++) if (matchers[m](fields[i])) return fields[i];
98
+ return null;
99
+ }
100
+ function raise(field, type) {
101
+ field.dispatchEvent(new Event(type, { bubbles: true }));
102
+ }
103
+ /**
104
+ * Creates the page tools for a document.
105
+ *
106
+ * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.
107
+ */
108
+ function createPageTools(options) {
109
+ var doc = options.doc;
110
+ var loc = options.loc;
111
+ return [
112
+ {
113
+ name: "page_get_info",
114
+ title: "Get page info",
115
+ description: "Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.",
116
+ inputSchema: {
117
+ type: "object",
118
+ properties: {}
119
+ },
120
+ annotations: { readOnlyHint: true },
121
+ execute: function() {
122
+ var meta = doc.querySelector(selectors.description);
123
+ var headings = [];
124
+ var all = doc.querySelectorAll(selectors.headings);
125
+ for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {
126
+ var text = cleanText(all[i].textContent);
127
+ if (text) headings.push({
128
+ level: (all[i].tagName || "").toUpperCase() === "H1" ? 1 : 2,
129
+ text
130
+ });
131
+ }
132
+ return {
133
+ title: doc.title,
134
+ url: loc.href,
135
+ description: meta ? meta.getAttribute("content") : null,
136
+ headings,
137
+ isAdmin: !!options.isAdmin
138
+ };
139
+ }
140
+ },
141
+ {
142
+ name: "page_list_links",
143
+ title: "List page links",
144
+ description: "Lists the links in the page's menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: { filter: {
148
+ type: "string",
149
+ description: "Only list links whose text contains these words."
150
+ } }
151
+ },
152
+ annotations: { readOnlyHint: true },
153
+ execute: function(input) {
154
+ var filter = cleanText(input && input.filter).toLowerCase();
155
+ var seen = /* @__PURE__ */ new Set();
156
+ var links = [];
157
+ var all = doc.querySelectorAll(selectors.links);
158
+ for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {
159
+ var url = resolveSameOrigin(all[i].getAttribute("href"), loc);
160
+ var text = cleanText(all[i].textContent);
161
+ if (!url || !text || seen.has(url.href) || filter && text.toLowerCase().indexOf(filter) < 0) continue;
162
+ seen.add(url.href);
163
+ links.push({
164
+ text,
165
+ url: url.href
166
+ });
167
+ }
168
+ return { links };
169
+ }
170
+ },
171
+ {
172
+ name: "page_navigate",
173
+ title: "Open page",
174
+ description: "Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.",
175
+ inputSchema: {
176
+ type: "object",
177
+ properties: { url: {
178
+ type: "string",
179
+ description: "The address to open, relative to the site or absolute on the same site."
180
+ } },
181
+ required: ["url"]
182
+ },
183
+ execute: function(input) {
184
+ var url = resolveSameOrigin(input && input.url, loc);
185
+ if (!url) throw new Error("Only addresses on this site can be opened.");
186
+ loc.assign(url.href);
187
+ return { navigatedTo: url.href };
188
+ }
189
+ },
190
+ {
191
+ name: "page_list_form_fields",
192
+ title: "List form fields",
193
+ description: "Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.",
194
+ inputSchema: {
195
+ type: "object",
196
+ properties: {}
197
+ },
198
+ annotations: { readOnlyHint: true },
199
+ execute: function() {
200
+ return { fields: listFields(doc).map(describeField) };
201
+ }
202
+ },
203
+ {
204
+ name: "page_set_form_field",
205
+ title: "Set form field",
206
+ description: "Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.",
207
+ inputSchema: {
208
+ type: "object",
209
+ properties: {
210
+ field: {
211
+ type: "string",
212
+ description: "The field's name, id or label, as page_list_form_fields lists it."
213
+ },
214
+ value: { description: "The value. For a check box, true or false. For a list, a choice's value or text." }
215
+ },
216
+ required: ["field", "value"]
217
+ },
218
+ execute: function(input) {
219
+ var field = findField(doc, input && input.field);
220
+ if (!field) throw new Error("No editable field on the page is named \"" + cleanText(input && input.field) + "\".");
221
+ var type = fieldType(field);
222
+ var value = input.value;
223
+ if (type === "checkbox" || type === "radio") field.checked = value === true || String(value).toLowerCase() === "true";
224
+ else if (type === "select") {
225
+ var wanted = cleanText(value).toLowerCase();
226
+ var option = Array.prototype.find.call(field.options || [], function(candidate) {
227
+ return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;
228
+ });
229
+ if (!option) throw new Error("The list \"" + fieldLabel(field) + "\" has no choice \"" + cleanText(value) + "\".");
230
+ field.value = option.value;
231
+ } else field.value = value == null ? "" : String(value);
232
+ raise(field, "input");
233
+ raise(field, "change");
234
+ return { field: describeField(field) };
235
+ }
236
+ }
237
+ ];
238
+ }
239
+ /**
240
+ * Registers the page tools with the page's ModelContext.
241
+ *
242
+ * @param {any} modelContext The page's ModelContext (document.modelContext).
243
+ * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.
244
+ * @param {AbortSignal} [signal] Unregisters the tools when aborted.
245
+ */
246
+ function register(modelContext, options, signal) {
247
+ return Promise.all(createPageTools(options).map(function(tool) {
248
+ return modelContext.registerTool(tool, signal ? { signal } : {}).catch(function(error) {
249
+ console.warn("The page tool \"" + tool.name + "\" could not be registered.", error);
250
+ });
251
+ }));
252
+ }
253
+ window.CrestAppsWebMcpPageTools = {
254
+ selectors,
255
+ createPageTools,
256
+ register
257
+ };
258
+ if (window.document && window.document.modelContext) {
259
+ var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;
260
+ register(window.document.modelContext, {
261
+ doc: window.document,
262
+ loc: window.location,
263
+ isAdmin: !!(config && config.isAdmin)
264
+ });
265
+ }
266
+ })();
267
+ //#endregion
268
+ })();
269
+ //# sourceMappingURL=webmcp-page-tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-page-tools.js","sources":["../../Assets/js/webmcp-page-tools.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP page tools.\n *\n * Tools that work on the page the user sees: what the page is, where its menus lead, opening another page of the same\n * site, and reading and filling its forms. They never submit a form and never leave the site, so the user always\n * reviews and saves the changes an agent makes.\n *\n * The tools are registered with the standard `document.modelContext.registerTool`, so any WebMCP agent can use them:\n * the browser's own assistant, or the CrestApps AI chat. As WebMCP defines, each answers a JSON-serializable value and\n * reports a failure by rejecting. Load webmcp-polyfill.js first in browsers without native WebMCP.\n */\n\n(function () {\n 'use strict';\n\n var MAX_HEADINGS = 20;\n var MAX_LINKS = 200;\n var MAX_FIELDS = 200;\n var MAX_VALUE_LENGTH = 2000;\n\n // The selectors the tools read the page with.\n var selectors = {\n description: 'meta[name=\"description\"]',\n headings: 'h1, h2',\n links: 'nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]',\n fields: 'form input, form select, form textarea',\n };\n\n // Field types an agent never reads or fills.\n var SKIPPED_TYPES = ['hidden', 'password', 'file', 'submit', 'button', 'reset', 'image'];\n var SKIPPED_NAMES = ['__RequestVerificationToken'];\n\n function cleanText(value) {\n return (value == null ? '' : String(value)).replace(/\\s+/g, ' ').trim();\n }\n\n\n function resolveSameOrigin(href, loc) {\n if (!href) {\n return null;\n }\n\n try {\n var url = new URL(href, loc.href);\n\n return url.origin === loc.origin ? url : null;\n } catch (error) {\n return null;\n }\n }\n\n function fieldType(field) {\n var tag = (field.tagName || '').toLowerCase();\n\n if (tag === 'select' || tag === 'textarea') {\n return tag;\n }\n\n return (field.type || 'text').toLowerCase();\n }\n\n function fieldLabel(field) {\n if (field.labels && field.labels.length > 0) {\n var label = cleanText(field.labels[0].textContent);\n\n if (label) {\n return label;\n }\n }\n\n return cleanText(field.getAttribute('aria-label')) || cleanText(field.getAttribute('placeholder'));\n }\n\n function isListed(field) {\n return !field.disabled &&\n SKIPPED_TYPES.indexOf(fieldType(field)) < 0 &&\n SKIPPED_NAMES.indexOf(field.name) < 0;\n }\n\n function listFields(doc) {\n var fields = [];\n var all = doc.querySelectorAll(selectors.fields);\n\n for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) {\n if (isListed(all[i])) {\n fields.push(all[i]);\n }\n }\n\n return fields;\n }\n\n function describeField(field) {\n var type = fieldType(field);\n var description = {\n key: field.name || field.id,\n name: field.name,\n id: field.id,\n label: fieldLabel(field),\n type: type,\n value: type === 'checkbox' || type === 'radio'\n ? !!field.checked\n : String(field.value == null ? '' : field.value).substring(0, MAX_VALUE_LENGTH),\n required: !!field.required,\n };\n\n if (type === 'select') {\n description.options = Array.prototype.map.call(field.options || [], function (option) {\n return { value: option.value, text: cleanText(option.text), selected: !!option.selected };\n });\n }\n\n return description;\n }\n\n function findField(doc, key) {\n var wanted = cleanText(key).toLowerCase();\n\n if (!wanted) {\n return null;\n }\n\n var fields = listFields(doc);\n var matchers = [\n function (field) {\n return (field.name || '').toLowerCase() === wanted;\n },\n function (field) {\n return (field.id || '').toLowerCase() === wanted;\n },\n function (field) {\n return fieldLabel(field).toLowerCase() === wanted;\n },\n ];\n\n for (var m = 0; m < matchers.length; m++) {\n for (var i = 0; i < fields.length; i++) {\n if (matchers[m](fields[i])) {\n return fields[i];\n }\n }\n }\n\n return null;\n }\n\n function raise(field, type) {\n field.dispatchEvent(new Event(type, { bubbles: true }));\n }\n\n /**\n * Creates the page tools for a document.\n *\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n */\n function createPageTools(options) {\n var doc = options.doc;\n var loc = options.loc;\n\n return [\n {\n name: 'page_get_info',\n title: 'Get page info',\n description: 'Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n var meta = doc.querySelector(selectors.description);\n var headings = [];\n var all = doc.querySelectorAll(selectors.headings);\n\n for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {\n var text = cleanText(all[i].textContent);\n\n if (text) {\n headings.push({ level: (all[i].tagName || '').toUpperCase() === 'H1' ? 1 : 2, text: text });\n }\n }\n\n return ({\n title: doc.title,\n url: loc.href,\n description: meta ? meta.getAttribute('content') : null,\n headings: headings,\n isAdmin: !!options.isAdmin,\n });\n },\n },\n {\n name: 'page_list_links',\n title: 'List page links',\n description: 'Lists the links in the page\\'s menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: { type: 'string', description: 'Only list links whose text contains these words.' },\n },\n },\n annotations: { readOnlyHint: true },\n execute: function (input) {\n var filter = cleanText(input && input.filter).toLowerCase();\n var seen = new Set();\n var links = [];\n var all = doc.querySelectorAll(selectors.links);\n\n for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {\n var url = resolveSameOrigin(all[i].getAttribute('href'), loc);\n var text = cleanText(all[i].textContent);\n\n if (!url || !text || seen.has(url.href) || (filter && text.toLowerCase().indexOf(filter) < 0)) {\n continue;\n }\n\n seen.add(url.href);\n links.push({ text: text, url: url.href });\n }\n\n return ({ links: links });\n },\n },\n {\n name: 'page_navigate',\n title: 'Open page',\n description: 'Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The address to open, relative to the site or absolute on the same site.' },\n },\n required: ['url'],\n },\n execute: function (input) {\n var url = resolveSameOrigin(input && input.url, loc);\n\n if (!url) {\n throw new Error('Only addresses on this site can be opened.');\n }\n\n loc.assign(url.href);\n\n return ({ navigatedTo: url.href });\n },\n },\n {\n name: 'page_list_form_fields',\n title: 'List form fields',\n description: 'Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return ({ fields: listFields(doc).map(describeField) });\n },\n },\n {\n name: 'page_set_form_field',\n title: 'Set form field',\n description: 'Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.',\n inputSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', description: 'The field\\'s name, id or label, as page_list_form_fields lists it.' },\n value: { description: 'The value. For a check box, true or false. For a list, a choice\\'s value or text.' },\n },\n required: ['field', 'value'],\n },\n execute: function (input) {\n var field = findField(doc, input && input.field);\n\n if (!field) {\n throw new Error('No editable field on the page is named \"' + cleanText(input && input.field) + '\".');\n }\n\n var type = fieldType(field);\n var value = input.value;\n\n if (type === 'checkbox' || type === 'radio') {\n field.checked = value === true || String(value).toLowerCase() === 'true';\n } else if (type === 'select') {\n var wanted = cleanText(value).toLowerCase();\n var option = Array.prototype.find.call(field.options || [], function (candidate) {\n return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;\n });\n\n if (!option) {\n throw new Error('The list \"' + fieldLabel(field) + '\" has no choice \"' + cleanText(value) + '\".');\n }\n\n field.value = option.value;\n } else {\n field.value = value == null ? '' : String(value);\n }\n\n raise(field, 'input');\n raise(field, 'change');\n\n return ({ field: describeField(field) });\n },\n },\n ];\n }\n\n /**\n * Registers the page tools with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext (document.modelContext).\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n */\n function register(modelContext, options, signal) {\n return Promise.all(createPageTools(options).map(function (tool) {\n return modelContext.registerTool(tool, signal ? { signal: signal } : {}).catch(function (error) {\n console.warn('The page tool \"' + tool.name + '\" could not be registered.', error);\n });\n }));\n }\n\n window.CrestAppsWebMcpPageTools = {\n selectors: selectors,\n createPageTools: createPageTools,\n register: register,\n };\n\n if (window.document && window.document.modelContext) {\n var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;\n\n register(window.document.modelContext, {\n doc: window.document,\n loc: window.location,\n isAdmin: !!(config && config.isAdmin),\n });\n }\n})();\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ (function(){(function(){"use strict";var e={description:`meta[name="description"]`,headings:`h1, h2`,links:`nav a[href], header a[href], aside a[href], [role="navigation"] a[href]`,fields:`form input, form select, form textarea`},t=[`hidden`,`password`,`file`,`submit`,`button`,`reset`,`image`],n=[`__RequestVerificationToken`];function r(e){return(e==null?``:String(e)).replace(/\s+/g,` `).trim()}function i(e,t){if(!e)return null;try{var n=new URL(e,t.href);return n.origin===t.origin?n:null}catch{return null}}function a(e){var t=(e.tagName||``).toLowerCase();return t===`select`||t===`textarea`?t:(e.type||`text`).toLowerCase()}function o(e){if(e.labels&&e.labels.length>0){var t=r(e.labels[0].textContent);if(t)return t}return r(e.getAttribute(`aria-label`))||r(e.getAttribute(`placeholder`))}function s(e){return!e.disabled&&t.indexOf(a(e))<0&&n.indexOf(e.name)<0}function c(t){for(var n=[],r=t.querySelectorAll(e.fields),i=0;i<r.length&&n.length<200;i++)s(r[i])&&n.push(r[i]);return n}function l(e){var t=a(e),n={key:e.name||e.id,name:e.name,id:e.id,label:o(e),type:t,value:t===`checkbox`||t===`radio`?!!e.checked:String(e.value==null?``:e.value).substring(0,2e3),required:!!e.required};return t===`select`&&(n.options=Array.prototype.map.call(e.options||[],function(e){return{value:e.value,text:r(e.text),selected:!!e.selected}})),n}function u(e,t){var n=r(t).toLowerCase();if(!n)return null;for(var i=c(e),a=[function(e){return(e.name||``).toLowerCase()===n},function(e){return(e.id||``).toLowerCase()===n},function(e){return o(e).toLowerCase()===n}],s=0;s<a.length;s++)for(var l=0;l<i.length;l++)if(a[s](i[l]))return i[l];return null}function d(e,t){e.dispatchEvent(new Event(t,{bubbles:!0}))}function f(t){var n=t.doc,s=t.loc;return[{name:`page_get_info`,title:`Get page info`,description:`Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){for(var i=n.querySelector(e.description),a=[],o=n.querySelectorAll(e.headings),c=0;c<o.length&&a.length<20;c++){var l=r(o[c].textContent);l&&a.push({level:(o[c].tagName||``).toUpperCase()===`H1`?1:2,text:l})}return{title:n.title,url:s.href,description:i?i.getAttribute(`content`):null,headings:a,isAdmin:!!t.isAdmin}}},{name:`page_list_links`,title:`List page links`,description:`Lists the links in the page's menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.`,inputSchema:{type:`object`,properties:{filter:{type:`string`,description:`Only list links whose text contains these words.`}}},annotations:{readOnlyHint:!0},execute:function(t){for(var a=r(t&&t.filter).toLowerCase(),o=new Set,c=[],l=n.querySelectorAll(e.links),u=0;u<l.length&&c.length<200;u++){var d=i(l[u].getAttribute(`href`),s),f=r(l[u].textContent);!d||!f||o.has(d.href)||a&&f.toLowerCase().indexOf(a)<0||(o.add(d.href),c.push({text:f,url:d.href}))}return{links:c}}},{name:`page_navigate`,title:`Open page`,description:`Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.`,inputSchema:{type:`object`,properties:{url:{type:`string`,description:`The address to open, relative to the site or absolute on the same site.`}},required:[`url`]},execute:function(e){var t=i(e&&e.url,s);if(!t)throw Error(`Only addresses on this site can be opened.`);return s.assign(t.href),{navigatedTo:t.href}}},{name:`page_list_form_fields`,title:`List form fields`,description:`Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.`,inputSchema:{type:`object`,properties:{}},annotations:{readOnlyHint:!0},execute:function(){return{fields:c(n).map(l)}}},{name:`page_set_form_field`,title:`Set form field`,description:`Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.`,inputSchema:{type:`object`,properties:{field:{type:`string`,description:`The field's name, id or label, as page_list_form_fields lists it.`},value:{description:`The value. For a check box, true or false. For a list, a choice's value or text.`}},required:[`field`,`value`]},execute:function(e){var t=u(n,e&&e.field);if(!t)throw Error(`No editable field on the page is named "`+r(e&&e.field)+`".`);var i=a(t),s=e.value;if(i===`checkbox`||i===`radio`)t.checked=s===!0||String(s).toLowerCase()===`true`;else if(i===`select`){var c=r(s).toLowerCase(),f=Array.prototype.find.call(t.options||[],function(e){return String(e.value).toLowerCase()===c||r(e.text).toLowerCase()===c});if(!f)throw Error(`The list "`+o(t)+`" has no choice "`+r(s)+`".`);t.value=f.value}else t.value=s==null?``:String(s);return d(t,`input`),d(t,`change`),{field:l(t)}}}]}function p(e,t,n){return Promise.all(f(t).map(function(t){return e.registerTool(t,n?{signal:n}:{}).catch(function(e){console.warn(`The page tool "`+t.name+`" could not be registered.`,e)})}))}if(window.CrestAppsWebMcpPageTools={selectors:e,createPageTools:f,register:p},window.document&&window.document.modelContext){var m=window.crestAppsWebMcp&&window.crestAppsWebMcp.config;p(window.document.modelContext,{doc:window.document,loc:window.location,isAdmin:!!(m&&m.isAdmin)})}})()})();
2
+ //# sourceMappingURL=webmcp-page-tools.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp-page-tools.min.js","sources":["../../Assets/js/webmcp-page-tools.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP page tools.\n *\n * Tools that work on the page the user sees: what the page is, where its menus lead, opening another page of the same\n * site, and reading and filling its forms. They never submit a form and never leave the site, so the user always\n * reviews and saves the changes an agent makes.\n *\n * The tools are registered with the standard `document.modelContext.registerTool`, so any WebMCP agent can use them:\n * the browser's own assistant, or the CrestApps AI chat. As WebMCP defines, each answers a JSON-serializable value and\n * reports a failure by rejecting. Load webmcp-polyfill.js first in browsers without native WebMCP.\n */\n\n(function () {\n 'use strict';\n\n var MAX_HEADINGS = 20;\n var MAX_LINKS = 200;\n var MAX_FIELDS = 200;\n var MAX_VALUE_LENGTH = 2000;\n\n // The selectors the tools read the page with.\n var selectors = {\n description: 'meta[name=\"description\"]',\n headings: 'h1, h2',\n links: 'nav a[href], header a[href], aside a[href], [role=\"navigation\"] a[href]',\n fields: 'form input, form select, form textarea',\n };\n\n // Field types an agent never reads or fills.\n var SKIPPED_TYPES = ['hidden', 'password', 'file', 'submit', 'button', 'reset', 'image'];\n var SKIPPED_NAMES = ['__RequestVerificationToken'];\n\n function cleanText(value) {\n return (value == null ? '' : String(value)).replace(/\\s+/g, ' ').trim();\n }\n\n\n function resolveSameOrigin(href, loc) {\n if (!href) {\n return null;\n }\n\n try {\n var url = new URL(href, loc.href);\n\n return url.origin === loc.origin ? url : null;\n } catch (error) {\n return null;\n }\n }\n\n function fieldType(field) {\n var tag = (field.tagName || '').toLowerCase();\n\n if (tag === 'select' || tag === 'textarea') {\n return tag;\n }\n\n return (field.type || 'text').toLowerCase();\n }\n\n function fieldLabel(field) {\n if (field.labels && field.labels.length > 0) {\n var label = cleanText(field.labels[0].textContent);\n\n if (label) {\n return label;\n }\n }\n\n return cleanText(field.getAttribute('aria-label')) || cleanText(field.getAttribute('placeholder'));\n }\n\n function isListed(field) {\n return !field.disabled &&\n SKIPPED_TYPES.indexOf(fieldType(field)) < 0 &&\n SKIPPED_NAMES.indexOf(field.name) < 0;\n }\n\n function listFields(doc) {\n var fields = [];\n var all = doc.querySelectorAll(selectors.fields);\n\n for (var i = 0; i < all.length && fields.length < MAX_FIELDS; i++) {\n if (isListed(all[i])) {\n fields.push(all[i]);\n }\n }\n\n return fields;\n }\n\n function describeField(field) {\n var type = fieldType(field);\n var description = {\n key: field.name || field.id,\n name: field.name,\n id: field.id,\n label: fieldLabel(field),\n type: type,\n value: type === 'checkbox' || type === 'radio'\n ? !!field.checked\n : String(field.value == null ? '' : field.value).substring(0, MAX_VALUE_LENGTH),\n required: !!field.required,\n };\n\n if (type === 'select') {\n description.options = Array.prototype.map.call(field.options || [], function (option) {\n return { value: option.value, text: cleanText(option.text), selected: !!option.selected };\n });\n }\n\n return description;\n }\n\n function findField(doc, key) {\n var wanted = cleanText(key).toLowerCase();\n\n if (!wanted) {\n return null;\n }\n\n var fields = listFields(doc);\n var matchers = [\n function (field) {\n return (field.name || '').toLowerCase() === wanted;\n },\n function (field) {\n return (field.id || '').toLowerCase() === wanted;\n },\n function (field) {\n return fieldLabel(field).toLowerCase() === wanted;\n },\n ];\n\n for (var m = 0; m < matchers.length; m++) {\n for (var i = 0; i < fields.length; i++) {\n if (matchers[m](fields[i])) {\n return fields[i];\n }\n }\n }\n\n return null;\n }\n\n function raise(field, type) {\n field.dispatchEvent(new Event(type, { bubbles: true }));\n }\n\n /**\n * Creates the page tools for a document.\n *\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n */\n function createPageTools(options) {\n var doc = options.doc;\n var loc = options.loc;\n\n return [\n {\n name: 'page_get_info',\n title: 'Get page info',\n description: 'Describes the page the user has open: its title, address, description and main headings, and whether it is an admin page.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n var meta = doc.querySelector(selectors.description);\n var headings = [];\n var all = doc.querySelectorAll(selectors.headings);\n\n for (var i = 0; i < all.length && headings.length < MAX_HEADINGS; i++) {\n var text = cleanText(all[i].textContent);\n\n if (text) {\n headings.push({ level: (all[i].tagName || '').toUpperCase() === 'H1' ? 1 : 2, text: text });\n }\n }\n\n return ({\n title: doc.title,\n url: loc.href,\n description: meta ? meta.getAttribute('content') : null,\n headings: headings,\n isAdmin: !!options.isAdmin,\n });\n },\n },\n {\n name: 'page_list_links',\n title: 'List page links',\n description: 'Lists the links in the page\\'s menus and navigation that stay on this site, with their text. Use it to find the page to open for a task.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: { type: 'string', description: 'Only list links whose text contains these words.' },\n },\n },\n annotations: { readOnlyHint: true },\n execute: function (input) {\n var filter = cleanText(input && input.filter).toLowerCase();\n var seen = new Set();\n var links = [];\n var all = doc.querySelectorAll(selectors.links);\n\n for (var i = 0; i < all.length && links.length < MAX_LINKS; i++) {\n var url = resolveSameOrigin(all[i].getAttribute('href'), loc);\n var text = cleanText(all[i].textContent);\n\n if (!url || !text || seen.has(url.href) || (filter && text.toLowerCase().indexOf(filter) < 0)) {\n continue;\n }\n\n seen.add(url.href);\n links.push({ text: text, url: url.href });\n }\n\n return ({ links: links });\n },\n },\n {\n name: 'page_navigate',\n title: 'Open page',\n description: 'Opens another page of this site in the current tab. Only addresses on this site are allowed. Unsaved changes on the current page are lost.',\n inputSchema: {\n type: 'object',\n properties: {\n url: { type: 'string', description: 'The address to open, relative to the site or absolute on the same site.' },\n },\n required: ['url'],\n },\n execute: function (input) {\n var url = resolveSameOrigin(input && input.url, loc);\n\n if (!url) {\n throw new Error('Only addresses on this site can be opened.');\n }\n\n loc.assign(url.href);\n\n return ({ navigatedTo: url.href });\n },\n },\n {\n name: 'page_list_form_fields',\n title: 'List form fields',\n description: 'Lists the fields of the forms on the page: name, label, type, current value, whether it is required and, for lists, the choices. Hidden, password and file fields are not listed.',\n inputSchema: { type: 'object', properties: {} },\n annotations: { readOnlyHint: true },\n execute: function () {\n return ({ fields: listFields(doc).map(describeField) });\n },\n },\n {\n name: 'page_set_form_field',\n title: 'Set form field',\n description: 'Puts a value into one form field on the page, found by its name, id or label. It does not save or submit the form: the user reviews the change and saves it.',\n inputSchema: {\n type: 'object',\n properties: {\n field: { type: 'string', description: 'The field\\'s name, id or label, as page_list_form_fields lists it.' },\n value: { description: 'The value. For a check box, true or false. For a list, a choice\\'s value or text.' },\n },\n required: ['field', 'value'],\n },\n execute: function (input) {\n var field = findField(doc, input && input.field);\n\n if (!field) {\n throw new Error('No editable field on the page is named \"' + cleanText(input && input.field) + '\".');\n }\n\n var type = fieldType(field);\n var value = input.value;\n\n if (type === 'checkbox' || type === 'radio') {\n field.checked = value === true || String(value).toLowerCase() === 'true';\n } else if (type === 'select') {\n var wanted = cleanText(value).toLowerCase();\n var option = Array.prototype.find.call(field.options || [], function (candidate) {\n return String(candidate.value).toLowerCase() === wanted || cleanText(candidate.text).toLowerCase() === wanted;\n });\n\n if (!option) {\n throw new Error('The list \"' + fieldLabel(field) + '\" has no choice \"' + cleanText(value) + '\".');\n }\n\n field.value = option.value;\n } else {\n field.value = value == null ? '' : String(value);\n }\n\n raise(field, 'input');\n raise(field, 'change');\n\n return ({ field: describeField(field) });\n },\n },\n ];\n }\n\n /**\n * Registers the page tools with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext (document.modelContext).\n * @param {{ doc: any, loc: any, isAdmin?: boolean }} options The document, its location, and whether it is an admin page.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n */\n function register(modelContext, options, signal) {\n return Promise.all(createPageTools(options).map(function (tool) {\n return modelContext.registerTool(tool, signal ? { signal: signal } : {}).catch(function (error) {\n console.warn('The page tool \"' + tool.name + '\" could not be registered.', error);\n });\n }));\n }\n\n window.CrestAppsWebMcpPageTools = {\n selectors: selectors,\n createPageTools: createPageTools,\n register: register,\n };\n\n if (window.document && window.document.modelContext) {\n var config = window.crestAppsWebMcp && window.crestAppsWebMcp.config;\n\n register(window.document.modelContext, {\n doc: window.document,\n loc: window.location,\n isAdmin: !!(config && config.isAdmin),\n });\n }\n})();\n"],"names":["selectors","SKIPPED_TYPES","SKIPPED_NAMES","cleanText","value","resolveSameOrigin","href","url","loc","fieldType","tag","field","fieldLabel","label","isListed","listFields","fields","all","doc","describeField","type","description","option","findField","wanted","key","matchers","m","i","raise","createPageTools","options","meta","headings","text","filter","input","seen","links","candidate","register","modelContext","tool","signal","error","config"],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 WebMCP polyfill contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,6 @@
1
+ var WebMCPPolyfill=(function(e){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});let t=[`consequentialHint`,`debugging`,`readOnlyHint`,`untrustedContentHint`],n=`webmcp-polyfill`,r=globalThis.DOMException;var i=class{#e;#t;#n;#r=new Set;#i=new Set;#a=Promise.withResolvers();#o=new Map;#s=new Set;#c;#l=!1;constructor(e,t){this.#t=e,this.#e=e.document,this.#n=t,this.#t.addEventListener(`message`,e=>this.#d(e),!0),this.#t.addEventListener(`pagehide`,()=>{this.#l=!0;for(let e of this.#s)e.end()}),this.#t.addEventListener(`pageshow`,e=>{e.persisted&&(this.#l=!1,this.#x())});for(let e of this.#x())this.#i.add(e);this.#i.size===0?this.#a.resolve():setTimeout(()=>{this.#i.clear(),this.#a.resolve()},500)}async allowed(){return this.#l?!1:this.#h(this.#e)}async getTools(e){await this.#a.promise;let t=this.#t.origin,n=[t,...e];return(await Promise.all(this.#b().map(async e=>{if(e===this.#t)return this.#n.getTools(t).map(e=>d(e,this.#t,t));try{let t=await this.#u(e,n,{kind:`getTools`});if(!Array.isArray(t.value))throw TypeError(`Expected a tool list from the frame`);return t.value.map(n=>d(f(n),e,t.origin))}catch(e){return e instanceof r&&e.name===`UnknownError`||console.warn(`WebMCP: could not read tools from a frame.`,e),[]}}))).flat()}async execute(e,t,n,r,i){let a=await this.#u(e,[t],{kind:`execute`,name:n,input:r},i);if(typeof a.value!=`string`)throw g();return a.value}async notify(e){await this.#a.promise;let t=[this.#t.origin,...e];for(let e of this.#b()){if(e===this.#t){await this.#n.changed();continue}try{await this.#u(e,t,{kind:`changed`})}catch{}}}#u(e,t,r,i){return new Promise((a,o)=>{if(i?.throwIfAborted(),this.#l||!this.#v(e)){o(g());return}let s=crypto.randomUUID(),{port1:c,port2:l}=new MessageChannel,u,d=()=>{clearTimeout(v),y(),i?.removeEventListener(`abort`,p),this.#o.delete(s),c.close()},f=(e=g())=>{c.postMessage({kind:`cancel`}),d(),o(e)},p=()=>f(i?.reason),h=()=>{u===void 0&&this.#C(e),f()},_=n=>{if(n.source===e){if(this.#o.delete(s),t&&!t.includes(n.origin)){f();return}u=n.origin,clearTimeout(v),r.kind!==`execute`&&r.kind!==`changed`&&(v=setTimeout(h,500)),c.postMessage(r)}},v=setTimeout(h,500),y=this.#w(e,f);c.onmessage=e=>{let t=m(e.data);if(t?.kind===`error`){f();return}t?.kind!==`result`||u===void 0||(d(),a({value:t.value,origin:u}))},c.onmessageerror=()=>f(),i?.addEventListener(`abort`,p,{once:!0}),this.#o.set(s,_);try{e.postMessage(`${n}:connect:${s}`,`*`,[l])}catch{l.close(),f()}})}#d(e){let t=p(e.data);if(!t)return;e.stopImmediatePropagation();let r=e.source;if(!e.isTrusted||!r||!h(r)||!this.#v(r)||e.origin===`null`||this.#l)return;if(t.kind===`announce`){let t=!this.#r.has(r);if(this.#S(r),r.postMessage(`${n}:present`,e.origin),!t||this.#t!==this.#t.top)return;for(let e of this.#r)e!==r&&e.postMessage(`${n}:refresh`,`*`);return}if(t.kind===`present`){this.#S(r);return}if(t.kind===`refresh`){r===this.#t.top&&this.#x();return}if(t.kind===`ready`){this.#o.get(t.id)?.(e);return}let[i]=e.ports;t.kind!==`connect`||!t.id||!i||e.ports.length!==1||(this.#S(r),this.#f(r,e.origin,t.id,i))}#f(e,t,r,i){let a=new AbortController,o=!1,s=()=>{clearTimeout(u),d(),i.close()},c=()=>{a.abort(),s()},l=()=>{i.postMessage({kind:`error`}),c()},u=setTimeout(l,500),d=this.#w(e,l);i.onmessageerror=l,i.onmessage=async n=>{let r=m(n.data);if(r?.kind===`cancel`){c();return}if(!o){o=!0,clearTimeout(u);try{let n=await this.#p(e,t,r,a.signal);i.postMessage({kind:`result`,value:n}),s()}catch{l()}}},e.postMessage(`${n}:ready:${r}`,t)}async#p(e,t,n,r){if(!n||!this.#v(e))throw g();if(n.kind===`permission`){let{childIndex:e,childOrigin:t}=n;if(typeof e!=`number`||!Number.isInteger(e)||e<0||typeof t!=`string`)throw g();return this.#m(e,t)}if(!await this.allowed()||!await this.#g(e,t)||!this.#v(e))throw g();if(r.throwIfAborted(),n.kind===`getTools`)return this.#n.getTools(t);if(n.kind===`execute`&&typeof n.name==`string`&&typeof n.input==`string`)return this.#n.execute(t,n.name,n.input,r);if(n.kind===`changed`){await this.#n.changed();return}throw g()}async#m(e,t){let n=this.#t[e];return n!==void 0&&await this.allowed()&&a(this.#e,n,t)}async#h(e){let t=l(e);if(!t)return!1;let n=u(e);if(n!==void 0)return n;if(t===t.parent)return!0;let r=t.location.ancestorOrigins?.[0],i=o(t.parent,r===void 0||r===t.origin);return i?a(i,t,t.origin)&&await this.#h(i):this.#_(t,t.origin)}async#g(e,t){let n=o(e,t===this.#t.origin);return n?this.#h(n):this.#_(e,t)}async#_(e,t){let n=e.parent;if(n===e)return!0;let r=-1;for(let t=0;t<n.length;t++)if(n[t]===e){r=t;break}if(r<0)return!1;if(n===this.#t)return await this.allowed()&&a(this.#e,e,t);if(await this.#a.promise,!this.#r.has(n))return!1;try{let e=await this.#u(n,void 0,{kind:`permission`,childIndex:r,childOrigin:t});return e.value===!0&&await this.#g(n,e.origin)}catch{return!1}}#v(e){try{return!e.closed&&e!==this.#t&&e.top===this.#t.top}catch{return!1}}#y(){if(this.#t.closed)return[];for(let e of this.#r)this.#v(e)||this.#r.delete(e);let e=[this.#t,...this.#r];for(let t=this.#t.parent;t!==t.parent;t=t.parent)e.push(t);let t=new Set,n=[this.#t.top];for(;n.length;){let r=n.pop();if(t.has(r))continue;t.add(r);let i=[];for(let e=0;e<r.length;e++){let t=r[e];t&&i.push(t)}for(let t of e)t!==r&&t.parent===r&&!i.includes(t)&&i.push(t);n.push(...i.reverse())}return Array.from(t).filter(e=>e===this.#t||this.#v(e))}#b(){return this.#y().filter(e=>e===this.#t||this.#r.has(e))}#x(){let e=this.#y().filter(e=>e!==this.#t);for(let t of e)t.postMessage(`${n}:announce`,`*`);return e}#S(e){this.#r.add(e),this.#i.delete(e),this.#i.size===0&&this.#a.resolve()}#C(e){this.#r.delete(e),this.#v(e)&&e.postMessage(`${n}:announce`,`*`)}#w(e,t){let n={peer:e,end:t};return this.#s.add(n),this.#c===void 0&&(this.#c=this.#t.setInterval(()=>{for(let e of this.#s)this.#v(e.peer)||e.end()},100)),()=>{this.#s.delete(n),this.#s.size===0&&(this.#t.clearInterval(this.#c),this.#c=void 0)}}};function a(e,t,n){let r=e.defaultView?.origin,i=s(e,t,n===r);if(!i)return!1;let a=(i.getAttribute(`allow`)??``).split(`;`).map(e=>e.trim().split(/\s+/u)).find(([e])=>e===`tools`);if(!a)return n===r;let o=a.length===1?[`'src'`]:a.slice(1);if(o.includes(`'none'`))return!1;let c=i.hasAttribute(`srcdoc`)?null:i.getAttribute(`src`),l=c?URL.parse(c,e.baseURI):null,u=!c||l?.protocol===`about:`?r:l?.origin;return o.some(e=>e===`*`||e===`'self'`&&n===r||e===`'src'`&&n===u||e===n)}function o(e,t){if(t)try{return e.document}catch{return}}function s(e,t,n){if(n)try{let n=t.frameElement;if(n?.ownerDocument===e)return n}catch{}return c(e,t)}function c(e,t){let n=e.querySelectorAll(`iframe, frame`);for(let e of n)if(e.contentWindow===t)return e;for(let n of e.querySelectorAll(`*`)){if(!n.shadowRoot)continue;let e=c(n.shadowRoot,t);if(e)return e}}function l(e){let t=e.defaultView;return t&&!t.closed&&t.document===e?t:void 0}function u(e){let t=e,n=m(t.permissionsPolicy??t.featurePolicy);if(typeof n?.features!=`function`||typeof n.allowsFeature!=`function`)return;let r=n.features();return Array.isArray(r)&&r.includes(`tools`)?!!n.allowsFeature(`tools`):void 0}function d(e,t,n){let r=e.serializedSchema===void 0?void 0:JSON.parse(e.serializedSchema);return{...e.annotations!==void 0&&{annotations:{...e.annotations}},description:e.description,...r!==void 0&&{inputSchema:r},name:e.name,origin:n,title:e.title,window:t}}function f(e){let n=m(e);if(!n||typeof n.name!=`string`||typeof n.title!=`string`||typeof n.description!=`string`||n.serializedSchema!==void 0&&typeof n.serializedSchema!=`string`)throw TypeError(`Invalid tool metadata from the frame`);let r={name:n.name,title:n.title,description:n.description,annotations:void 0,serializedSchema:n.serializedSchema};if(n.annotations===void 0)return r;let i=m(n.annotations);if(!i)throw TypeError(`Invalid tool annotations from the frame`);let a={};for(let e of t){let t=i[e];if(t!==void 0){if(typeof t!=`boolean`)throw TypeError(`Invalid ${e} annotation from the frame`);a[e]=t}}return r.annotations=a,r}function p(e){if(typeof e!=`string`)return;let[t,r=``,i=``,a]=e.split(`:`);return t===n&&a===void 0?{kind:r,id:i}:void 0}function m(e){if(!(typeof e!=`object`||!e||Array.isArray(e)))return e}function h(e){try{return`postMessage`in e&&`window`in e}catch{return!1}}function g(){return new r(`Tool execution failed`,`UnknownError`)}let _=new WeakSet,v=new WeakMap,y=new WeakMap;function b(e){return e.isTrusted&&(_.has(e)||e.eventPhase!==Event.NONE&&e.target instanceof HTMLFormElement&&y.has(e.target))}function ee(e,t){if(!b(e))throw new DOMException(`respondWith() is only available during an agent-invoked submit event`,`InvalidStateError`);if(!e.defaultPrevented)throw new DOMException(`respondWith() requires preventDefault() during an agent-invoked submit event`,`InvalidStateError`);if(e.eventPhase===Event.NONE)throw new DOMException(`respondWith() is only available while the submit event is being dispatched`,`InvalidStateError`);_.add(e);let n=Promise.resolve(t);v.set(e,n),e.target instanceof HTMLFormElement&&y.get(e.target)?.respond(e)}let x=new Set([`email`,`password`,`search`,`tel`,`text`,`url`]),te=new Set([...x,`date`,`datetime-local`,`month`,`number`,`time`,`week`]);function ne(e){return e instanceof HTMLInputElement||e instanceof HTMLSelectElement||e instanceof HTMLTextAreaElement}function S(e){return Object.getOwnPropertyDescriptor(HTMLFormElement.prototype,`elements`)?.get?.call(e)}function C(e,t){return Element.prototype.getAttribute.call(e,t)}function w(e,t){return Element.prototype.hasAttribute.call(e,t)}function T(e){return Object.getOwnPropertyDescriptor(Node.prototype,`isConnected`)?.get?.call(e)}function re(e){return Object.getOwnPropertyDescriptor(Element.prototype,`shadowRoot`)?.get?.call(e)}function ie(e){return HTMLFormElement.prototype.checkValidity.call(e)}function ae(e,t){HTMLFormElement.prototype.requestSubmit.call(e,t)}function oe(e){return[...S(e)].filter(e=>ne(e)&&!e.matches(`:disabled`)&&!(`readOnly`in e&&e.readOnly&&(e instanceof HTMLTextAreaElement||te.has(e.type))))}function se(e){let t=new Map;for(let n of oe(e)){let e=n.name.trim(),r=t.get(e);r?r.push(n):t.set(e,[n])}return t}function ce(e){return[...e.labels??[]].map(e=>{let t=e.cloneNode(!0);return t instanceof HTMLElement?(t.querySelectorAll(`button, input, meter, output, progress, select, textarea`).forEach(e=>e.remove()),t.textContent?.trim()??``):``}).filter(Boolean).join(`; `)}function le(e,t){for(let n=t[0]?.parentElement;n&&n!==e;n=n.parentElement)if(n instanceof HTMLFieldSetElement&&t.every(e=>n.contains(e)))return n}function ue(e,t){if(t.length===1){let e=t[0];return e&&(e.getAttribute(`toolparamdescription`)||ce(e)||e.getAttribute(`aria-description`))||void 0}return le(e,t)?.getAttribute(`toolparamdescription`)||void 0}function de(e,t,n,r){let i=ue(t,n),a=i&&r?`${i} (${r})`:i||r;return a?{...e,description:a}:e}function fe(e,t){let n=e.getAttribute(t);if(n===null||n.trim()===``)return;let r=Number(n);return Number.isFinite(r)?r:void 0}function pe(e,t){let n=Number(e.getAttribute(`step`));return Number.isFinite(n)&&n>0?n:t}function me(e,t){let n=e/t;return Math.abs(n-Math.round(n))<2**-52*16}function E(e,t){let n=t.getAttribute(`pattern`);if(n===null)return e;try{new RegExp(n,`v`)}catch{return e}return{...e,pattern:n}}function D(e){let t={type:`number`},n=fe(e,`min`),r=fe(e,`max`);if(n!==void 0&&(t.minimum=n),r!==void 0&&(t.maximum=r),e.getAttribute(`step`)!==`any`){let r=pe(e,1),i=Number(e.getAttribute(`value`));me(n??(Number.isFinite(i)?i:0),r)&&(t.multipleOf=r)}return t}function O(e,t){let n=pe(e,60);return n<1?`${t}(:[0-5][0-9](\\.[0-9]{1,3})?)?$`:n<60?`${t}(:[0-5][0-9])?$`:`${t}$`}function he(e){return{anyOf:e.map(e=>({type:`string`,const:e.value,title:e.textContent??``})),enum:e.map(e=>e.value)}}function k(e){return{anyOf:e.map(e=>{let t=ce(e),n={type:`string`,const:e.value};return t&&(n.title=t),n}),enum:e.map(e=>e.value)}}function A(e,t){return e instanceof HTMLInputElement&&e.type===t}function ge(e){if(x.has(e.type))return E({type:`string`},e);switch(e.type){case`hidden`:return e.getAttribute(`toolparamdescription`)?{type:`string`}:void 0;case`number`:return E(D(e),e);case`range`:{let t=D(e);return t.minimum??=0,t.maximum??=100,t}case`checkbox`:return{type:`boolean`};case`date`:return{type:`string`,format:`date`};case`month`:return{type:`string`,format:`^[0-9]{4}-(0[1-9]|1[0-2])$`};case`week`:return{type:`string`,format:`^[0-9]{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$`};case`time`:return{type:`string`,format:O(e,`^([01][0-9]|2[0-3]):[0-5][0-9]`)};case`datetime-local`:return{type:`string`,format:O(e,`^[0-9]{4}-(0[1-9]|1[0-2])-[0-9]{2}T([01][0-9]|2[0-3]):[0-5][0-9]`)};case`color`:return{type:`string`,format:`^#[0-9a-zA-Z]{6}$`};default:return}}function j(e,t){let[n]=t;if(!n)return;let r;if(t.every(e=>A(e,`radio`)))r={type:`string`,...k(t)};else if(t.length>1)t.every(e=>A(e,`checkbox`))&&(r={type:`array`,items:{type:`string`,...k(t)},uniqueItems:!0});else if(n instanceof HTMLTextAreaElement)r={type:`string`};else if(n instanceof HTMLSelectElement){let e=he([...n.options]);r=n.multiple?{type:`array`,items:{type:`string`,...e},uniqueItems:!0}:{type:`string`,...e}}else r=ge(n);let i=n.type===`date`?`Dates MUST be provided in 'YYYY-MM-DD' format.`:void 0;return r&&de(r,e,t,i)}function _e(e){let t={},n=[];for(let[r,i]of se(e)){if(!r)continue;let a=j(e,i);a&&(Object.defineProperty(t,r,{configurable:!0,enumerable:!0,value:a,writable:!0}),i.some(e=>e.required)&&n.push(r))}return{type:`object`,properties:t,required:n}}function ve(e){return{name:C(e,`toolname`)??``,title:C(e,`tooltitle`)??``,description:C(e,`tooldescription`)??``,inputSchema:_e(e),autosubmit:w(e,`toolautosubmit`)}}function M(e){if(typeof e==`string`||typeof e==`boolean`||typeof e==`number`&&Number.isFinite(e))return String(e)}function N(e){if(typeof e==`boolean`)return e;if(typeof e==`number`&&Number.isInteger(e))return e!==0;if(typeof e==`string`){if(e===`1`||e.toLowerCase()===`true`)return!0;if(e===`0`||e.toLowerCase()===`false`)return!1}}function P(e,t){if(!Array.isArray(e))return!1;let n=new Set(t);for(let t of e){let e=M(t);if(e===void 0||!n.delete(e))return!1}return!0}function ye(e,t){if(t===``)return e.type!==`number`&&e.type!==`range`;let n=e.ownerDocument.createElement(`input`);return n.type=e.type,n.value=t,n.value!==``}function be(e,t,n){let[r]=t;if(!r||!j(e,t))return!1;let i=M(n);if(t.every(e=>A(e,`radio`)))return t.some(e=>e.value===i);if(t.length>1)return P(n,new Set(t.map(e=>e.value)));if(r instanceof HTMLSelectElement){let e=new Set([...r.options].map(e=>e.value));return r.multiple?P(n,e):i!==void 0&&e.has(i)}return r instanceof HTMLTextAreaElement?i!==void 0:r.type===`checkbox`?N(n)!==void 0:i!==void 0&&ye(r,i)}function F(e){e.dispatchEvent(new Event(`input`,{bubbles:!0,composed:!0})),e.dispatchEvent(new Event(`change`,{bubbles:!0}))}function xe(e,t){if(e.value===t)return;let n=e instanceof HTMLInputElement?HTMLInputElement.prototype:HTMLTextAreaElement.prototype;Object.getOwnPropertyDescriptor(n,`value`)?.set?.call(e,t),F(e)}function I(e,t){e.checked!==t&&(Object.getOwnPropertyDescriptor(HTMLInputElement.prototype,`checked`)?.set?.call(e,t),F(e))}function Se(e,t){let[n]=e,r=M(t),i=new Set(Array.isArray(t)?t.map(M):[]);if(e.every(e=>A(e,`radio`))){let t=e.find(e=>e.value===r);t&&I(t,!0)}else if(e.length>1)for(let t of e)t instanceof HTMLInputElement&&I(t,i.has(t.value));else if(n instanceof HTMLSelectElement)if(n.multiple){let e=!1;for(let t of n.options){let n=i.has(t.value);t.selected!==n&&(t.selected=n,e=!0)}e&&F(n)}else r!==void 0&&n.value!==r&&(n.value=r,F(n));else if(n&&A(n,`checkbox`)){let e=N(t);e!==void 0&&I(n,e)}else n&&r!==void 0&&xe(n,r)}function Ce(e,t){if(Array.isArray(t))throw g();let n=se(e),r=[];for(let[i,a]of Object.entries(t)){let t=n.get(i);if(!t||!be(e,t,a))throw g();r.push({controls:t,value:a})}for(let{controls:e,value:t}of r)Se(e,t)}function we(e){return[...S(e)].find(e=>!e.matches(`:disabled`)&&(e instanceof HTMLButtonElement&&e.type===`submit`||e instanceof HTMLInputElement&&[`image`,`submit`].includes(e.type)))}function L(e,t){let n=new Event(e);return Object.defineProperty(n,"toolName",{enumerable:!0,value:t}),n}function Te(e,t,n,r,i){let{form:a}=t;return t.cancelPending?.(g()),new Promise((o,s)=>{let c=!1,l=()=>{EventTarget.prototype.removeEventListener.call(a,`invalid`,p,!0),i.removeEventListener(`abort`,m),y.delete(a),t.cancelPending===d&&delete t.cancelPending},u=e=>{c||(c=!0,l(),e())},d=e=>u(()=>s(e)),f=e=>{t.cancelPending===d&&delete t.cancelPending,e.then(e=>u(()=>o(e??null)),e=>u(()=>s(e)))},p=e=>{e.isTrusted&&queueMicrotask(()=>{ie(a)||d(g())})},m=()=>{u(()=>{e.dispatchEvent(L(`toolcancel`,n.name)),s(i.reason)})};if(y.set(a,{complete(e){setTimeout(()=>{let t=v.get(e);t?f(t):e.defaultPrevented?d(g()):u(()=>o(null))})},direct(){u(()=>o(null))},respond(e){queueMicrotask(()=>{let t=v.get(e);t&&f(t)})}}),t.cancelPending=d,EventTarget.prototype.addEventListener.call(a,`invalid`,p,!0),i.addEventListener(`abort`,m,{once:!0}),!n.autosubmit){r?.focus(),e.dispatchEvent(L(`toolactivated`,n.name));return}try{ae(a,r),e.dispatchEvent(L(`toolactivated`,n.name))}catch(e){d(e)}})}function Ee(e){let t=SubmitEvent.prototype;if(`agentInvoked`in t&&`respondWith`in t)return;`agentInvoked`in t||Object.defineProperty(t,"agentInvoked",{configurable:!0,enumerable:!0,get(){return b(this)}}),`respondWith`in t||Object.defineProperty(t,"respondWith",{configurable:!0,enumerable:!0,writable:!0,value(e){ee(this,e)}});let n=new Map,r=new Map,i=new Map,a=e=>{if(!(e instanceof SubmitEvent)||!e.isTrusted||!(e.target instanceof HTMLFormElement))return;let t=y.get(e.target);t&&(_.add(e),t.complete(e))},o=e=>{if(!e.isTrusted||!(e.target instanceof HTMLFormElement))return;let t=e.target;setTimeout(()=>{e.defaultPrevented||n.get(t)?.cancelPending?.(g())})};function s(e){i.get(e)?.disconnect(),i.delete(e),e.removeEventListener(`reset`,o,!0),e.removeEventListener(`submit`,a,!0)}function c(e){if(i.has(e))return;let t=new MutationObserver(l);i.set(e,t),t.observe(e,{attributes:!0,characterData:!0,childList:!0,subtree:!0}),e.addEventListener(`reset`,o,!0),e.addEventListener(`submit`,a,!0)}function l(){c(document);for(let e of i.keys())e instanceof ShadowRoot&&!e.host.isConnected&&s(e);let t=new Set,a=new Map,o=new Map;for(let e of i.keys())for(let i of e.querySelectorAll(`*`)){let e=re(i);if(e&&c(e),!(i instanceof HTMLFormElement)||!w(i,`toolname`)||!w(i,`tooldescription`)||!T(i))continue;let s=i;t.add(s);let l=ve(s),u=JSON.stringify(l);if(r.get(s)===u)continue;r.delete(s);let d=o.get(l.name);if(d){let e=e=>n.get(e)?.name===l.name;if(!e(s)||e(d)){r.set(s,u);continue}let t=a.get(d);t&&r.set(d,t.fingerprint),a.delete(d)}o.set(l.name,s),a.set(s,{definition:l,fingerprint:u})}for(let e of r.keys())t.has(e)||r.delete(e);for(let[e,t]of n)a.get(e)?.fingerprint!==t.fingerprint&&(t.cancelPending?.(g()),t.controller.abort(),n.delete(e));for(let[t,{definition:r,fingerprint:i}]of a){if(n.has(t))continue;let a=new AbortController,o={controller:a,fingerprint:i,form:t,name:r.name};n.set(t,o),e.registerTool({name:r.name,title:r.title,description:r.description,inputSchema:r.inputSchema,execute(n,i){let a=we(t);if(!r.autosubmit&&!a)throw g();return Ce(t,n),Te(e,o,r,a,i.signal)}},{signal:a.signal}).catch(e=>{a.abort(),!(e instanceof DOMException&&e.name===`AbortError`)&&console.error(`[webmcp] declarative form tool "${r.name}" was not registered:`,e)})}}let u=Object.getOwnPropertyDescriptor(Element.prototype,`attachShadow`);if(u?.configurable){let e=Element.prototype.attachShadow,t=function(t){let n=e.call(this,t);return n.mode===`open`&&Object.getOwnPropertyDescriptor(Node.prototype,`ownerDocument`)?.get?.call(this)===document&&T(this)&&c(n),n};Object.defineProperty(Element.prototype,"attachShadow",{...u,value:t})}let d=Object.getOwnPropertyDescriptor(HTMLFormElement.prototype,`submit`);if(d?.configurable){let e=HTMLFormElement.prototype.submit,t=function(){e.call(this),y.get(this)?.direct()};Object.defineProperty(HTMLFormElement.prototype,"submit",{...d,value:t})}l()}
2
+ /*!
3
+ * Copyright (c) 2026 WebMCP polyfill contributors
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+ let R=globalThis.DOMException,De=Object.getOwnPropertyDescriptor(globalThis,`window`)?.get,z=new WeakMap;function Oe(){if(typeof document>`u`||!globalThis.isSecureContext||`modelContext`in document)return;let e=Document.prototype,t=Object.getOwnPropertyDescriptor(e,`defaultView`).get,n=Object.getOwnPropertyDescriptor(globalThis,`ModelContext`);if(!Object.isExtensible(e)||n&&!n.configurable||!n&&!Object.isExtensible(globalThis))throw TypeError(`Cannot install WebMCP on this realm`);Object.defineProperty(globalThis,"ModelContext",{value:H,configurable:!0,writable:!0});let{getModelContext:r}={getModelContext(){t.call(this);let e=z.get(this);return e||(e=new B(this),z.set(this,e)),e}};Object.defineProperty(r,"name",{value:`get modelContext`}),Object.defineProperty(e,"modelContext",{configurable:!0,enumerable:!0,get:r}),document.modelContext}var B=class extends EventTarget{#e;#t=new Map;#n;#r=null;#i=e=>{let t=this.#r;typeof t==`function`&&Reflect.apply(t,this,[e])===!1&&Event.prototype.preventDefault.call(e)};constructor(e){super(),this.#e=e;let t=l(e);t&&(this.#n=new i(t,{getTools:e=>this.#s(e),execute:(t,n,r,i)=>{let a=Z(e).origin;return this.#a(n,r,a,t,i)},changed:()=>this.#c()}))}get ontoolchange(){return this.#r}set ontoolchange(e){let t=W(e)?e:null;!this.#r&&t&&this.addEventListener(`toolchange`,this.#i),this.#r&&!t&&this.removeEventListener(`toolchange`,this.#i),this.#r=t}async registerTool(e,t={}){let n=this.#e,{name:r,title:i,description:a,annotations:o,inputSchema:s,execute:c}=ke(e),l=G(t),u=Ne(l.exposedTo),d=Me(l.signal);Z(n);let f=()=>{if(this.#t.has(r))throw new R(`A tool named ${r} is already registered`,`InvalidStateError`)};if(f(),!/^[A-Za-z0-9_.-]{1,128}$/u.test(r))throw new R(`Tool names are 1 to 128 characters of ASCII alphanumerics, "_", "-" or ".": ${r}`,`InvalidStateError`);if(!a)throw new R(`A tool description cannot be empty`,`InvalidStateError`);let p=s===void 0?void 0:Y(s);d?.throwIfAborted();let m=X(u),h={metadata:{name:r,title:i,description:a,annotations:o,serializedSchema:p},execute:c,exposedTo:m},g=await this.#o();return d?.throwIfAborted(),f(),new Promise((e,t)=>{d?.addEventListener(`abort`,()=>{this.#t.delete(r),g.notify(m),t(d.reason)},{once:!0}),this.#t.set(r,h),g.notify(m).then(e,t)})}async getTools(e={}){let t=this.#e,n=Ne(G(e).fromOrigins);Z(t);let r=await this.#o(),i=X(n),a=await r.getTools(i);return a.sort((e,t)=>e.name===t.name?0:e.name<t.name?-1:1),new Promise(e=>{$(()=>e(a))})}async executeTool(e,t={},n={}){let r=this.#e,i=Ae(e),a=Me(G(n).signal);Z(r);let o=URL.parse(i.origin)?.origin;if(!o||o===`null`)throw new R(`Invalid or opaque origin`,`NotSupportedError`);if(!W(t))throw TypeError(`inputObject must be an object`);let s=Y(t);a?.throwIfAborted();let c=await this.#o();a?.throwIfAborted();let l=Z(r);return i.window===l?this.#a(i.name,s,o,l.origin,a):c.execute(i.window,o,i.name,s,a)}#a(e,t,n,r,i){return new Promise((a,o)=>{i?.throwIfAborted();let s=new AbortController,c=!1,l=()=>{o(i.reason),$(()=>{c||s.abort()})},u=()=>{c=!0,$(()=>{i?.removeEventListener(`abort`,l),o(g())})},d=e=>{if(c=!0,!i?.aborted)try{let t=Y(e);$(()=>{i?.removeEventListener(`abort`,l),a(t)})}catch{u()}};i?.addEventListener(`abort`,l,{once:!0}),$(()=>{if(!i?.aborted)try{let i=Z(this.#e),a=this.#t.get(e);if(!a||n!==i.origin||!V(a,i.origin,r)){u();return}let o=JSON.parse(t);if(!W(o)){u();return}let c=a.execute,l=c(o,{signal:s.signal});Promise.resolve(l).then(d,u)}catch{u()}})})}async#o(){let e=this.#n;if(!e||!await e.allowed())throw new R(`WebMCP is disabled by Permissions Policy`,`NotAllowedError`);return Z(this.#e),e}#s(e){let t=Z(this.#e).origin,n=[];for(let r of this.#t.values())V(r,t,e)&&n.push(r.metadata);return n}#c(){return new Promise(e=>{$(()=>{this.dispatchEvent(new Event(`toolchange`)),e()})})}};function V(e,t,n){return t===n||e.exposedTo.includes(n)}let H=function(){throw TypeError(`Illegal constructor`)};Object.defineProperty(H,"name",{value:`ModelContext`}),Object.defineProperty(H,"prototype",{value:B.prototype,writable:!1}),Object.setPrototypeOf(H,EventTarget),Object.defineProperties(B.prototype,{constructor:{value:H,configurable:!0,writable:!0},[Symbol.toStringTag]:{value:`ModelContext`,configurable:!0},registerTool:{enumerable:!0},getTools:{enumerable:!0},executeTool:{enumerable:!0},ontoolchange:{enumerable:!0}});function ke(e){let t=G(e),n=je(t.annotations),r=K(J(t.description,`description`)),i=J(t.execute,`execute`);if(typeof i!=`function`)throw TypeError(`execute must be a function`);let a=i,o=U(t.inputSchema),s=K(J(t.name,`name`)),c=t.title;return{name:s,title:c===void 0?``:q(c),description:r,annotations:n,inputSchema:o,execute:a}}function Ae(e){let t=G(e);je(t.annotations),K(J(t.description,`description`)),U(t.inputSchema);let n=K(J(t.name,`name`)),r=q(J(t.origin,`origin`)),i=t.title;i!==void 0&&K(i);let a=J(t.window,`window`);if(!W(a))throw TypeError(`window must be a Window`);return De.call(a),{name:n,origin:r,window:a}}function U(e){if(e!==void 0&&!W(e))throw TypeError(`inputSchema must be an object`);return e}function W(e){return typeof e==`object`&&!!e||typeof e==`function`}function G(e){if(e==null)return{};if(!W(e))throw TypeError(`Expected a dictionary`);return e}function je(e){if(e===void 0)return;let n=G(e),r={};for(let e of t)r[e]=!!n[e];return r}function K(e){if(typeof e==`symbol`)throw TypeError(`Cannot convert a Symbol to a string`);return String(e)}function q(e){return K(e).toWellFormed()}function J(e,t){if(e===void 0)throw TypeError(`${t} is required`);return e}function Y(e){let t=JSON.stringify(e);if(t===void 0)throw TypeError(`Value is not JSON-serializable`);return t}function Me(e){if(e!==void 0)return AbortSignal.any([e])}function Ne(e){if(e===void 0)return[];if(!W(e))throw TypeError(`Origins must be a sequence`);let t=G(e)[Symbol.iterator];if(typeof t!=`function`)throw TypeError(`Origins must be a sequence`);let n={[Symbol.iterator](){let n=Reflect.apply(t,e,[]);if(!W(n))throw TypeError(`Iterator must be an object`);let r=G(n).next;if(typeof r!=`function`)throw TypeError(`Iterator next must be a function`);return{next(){return Reflect.apply(r,n,[])}}}};return Array.from(n,q)}function X(e){return e.map(e=>{let t=URL.parse(e)?.origin;if(t===void 0)throw new R(`Invalid origin`,`SecurityError`);let n=URL.parse(t);if(!n||!Pe(n))throw new R(`Origin is not potentially trustworthy`,`SecurityError`);return t})}function Pe({protocol:e,hostname:t}){if([`https:`,`wss:`].includes(e))return!0;let n=t===`[::1]`||/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(t),r=t===`localhost`||t===`localhost.`||t.endsWith(`.localhost`)||t.endsWith(`.localhost.`);return[`http:`,`ws:`].includes(e)&&(n||r)}function Z(e){let t=l(e);if(!t)throw new R(`The document is not fully active`,`InvalidStateError`);if(u(e)===!1)throw new R(`WebMCP is disabled by Permissions Policy`,`NotAllowedError`);return t}let Q,Fe=[];function $(e){if(!Q){let e=new MessageChannel;e.port1.onmessage=()=>Fe.shift()?.(),Q=e.port2}Fe.push(e),Q.postMessage(void 0)}function Ie(){if(typeof document>`u`||!globalThis.isSecureContext||typeof Promise.withResolvers!=`function`||typeof AbortSignal.any!=`function`||typeof String.prototype.toWellFormed!=`function`||typeof URL.parse!=`function`)return;Oe();let e=document.modelContext;e&&Ee(e)}try{Ie()}catch(e){console.error(`[WebMCPPolyfill] Auto-initialization failed:`,e)}return e.installWebMCP=Ie,e})({});
package/dist/webmcp.js ADDED
@@ -0,0 +1,282 @@
1
+ /*
2
+ ** NOTE: This file is generated by the assets build (npm run build) and should not be edited directly!
3
+ ** Edit its sources under Assets/ and rebuild; see Assets.json for the files it is built from.
4
+ */
5
+ (function() {
6
+ //#region src/Resources/CrestApps.AI.WebMcp.Resources/Assets/js/webmcp.js
7
+ (function() {
8
+ "use strict";
9
+ var CONFIG_ELEMENT_ID = "crestapps-webmcp-config";
10
+ var MAX_PAGE_TOOLS = 64;
11
+ var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;
12
+ /**
13
+ * Finds the page's WebMCP ModelContext.
14
+ *
15
+ * @param {any} doc The document.
16
+ */
17
+ function getModelContext(doc) {
18
+ return doc && doc.modelContext ? doc.modelContext : null;
19
+ }
20
+ /**
21
+ * Reads the configuration block the site writes into the page.
22
+ *
23
+ * @param {any} doc The document.
24
+ */
25
+ function readConfig(doc) {
26
+ var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;
27
+ if (!element) return null;
28
+ try {
29
+ return JSON.parse(element.textContent);
30
+ } catch (error) {
31
+ return null;
32
+ }
33
+ }
34
+ /**
35
+ * Creates the standard tool that calls one of the site's AI tools.
36
+ *
37
+ * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.
38
+ * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.
39
+ * @param {Function} fetchImpl The fetch function.
40
+ */
41
+ function createServerTool(descriptor, config, fetchImpl) {
42
+ if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) throw new TypeError("A site tool needs a name and an address to call.");
43
+ var url = config.invokeUrlTemplate.replace("{name}", encodeURIComponent(descriptor.name));
44
+ /** @type {any} */
45
+ var tool = {
46
+ name: descriptor.name,
47
+ description: descriptor.description,
48
+ annotations: { untrustedContentHint: true },
49
+ execute: async function(input) {
50
+ /** @type {Record<string, string>} */
51
+ var headers = { "Content-Type": "application/json" };
52
+ if (config.antiforgeryHeaderName && config.antiforgeryToken) headers[config.antiforgeryHeaderName] = config.antiforgeryToken;
53
+ var response = await fetchImpl(url, {
54
+ method: "POST",
55
+ credentials: "same-origin",
56
+ headers,
57
+ body: JSON.stringify(input || {})
58
+ });
59
+ if (!response.ok) throw new Error("The site refused the tool call (" + response.status + ").");
60
+ var data = await response.json();
61
+ var text = (Array.isArray(data.content) ? data.content : []).filter(function(part) {
62
+ return part && part.type === "text";
63
+ }).map(function(part) {
64
+ return part.text == null ? "" : String(part.text);
65
+ }).join("\n");
66
+ if (data.isError) throw new Error(text || "The tool failed.");
67
+ return text;
68
+ }
69
+ };
70
+ if (descriptor.title) tool.title = descriptor.title;
71
+ if (descriptor.inputSchema) tool.inputSchema = descriptor.inputSchema;
72
+ return tool;
73
+ }
74
+ /**
75
+ * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.
76
+ *
77
+ * @param {any} modelContext The page's ModelContext.
78
+ * @param {any} config The page configuration.
79
+ * @param {Function} fetchImpl The fetch function.
80
+ * @param {AbortSignal} [signal] Unregisters the tools when aborted.
81
+ * @returns {Promise<string[]>} The names of the tools registered.
82
+ */
83
+ async function registerServerTools(modelContext, config, fetchImpl, signal) {
84
+ if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) return [];
85
+ var descriptors;
86
+ try {
87
+ var response = await fetchImpl(config.toolsUrl, {
88
+ credentials: "same-origin",
89
+ headers: { Accept: "application/json" }
90
+ });
91
+ if (!response.ok) return [];
92
+ var data = await response.json();
93
+ descriptors = data && Array.isArray(data.tools) ? data.tools : [];
94
+ } catch (error) {
95
+ console.warn("The site's WebMCP tools could not be loaded.", error);
96
+ return [];
97
+ }
98
+ /** @type {string[]} */
99
+ var names = [];
100
+ for (var i = 0; i < descriptors.length; i++) try {
101
+ await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal } : {});
102
+ names.push(descriptors[i].name);
103
+ } catch (error) {
104
+ console.warn("A site WebMCP tool could not be registered.", error);
105
+ }
106
+ return names;
107
+ }
108
+ /**
109
+ * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with
110
+ * their schemas as JSON text and their annotations as flags.
111
+ *
112
+ * @param {Array<any>} tools The RegisteredTool records getTools() answered.
113
+ * @param {Set<string>} [excludedNames] The names to leave out.
114
+ */
115
+ function toPageToolDescriptors(tools, excludedNames) {
116
+ /** @type {Array<any>} */
117
+ var descriptors = [];
118
+ (tools || []).forEach(function(tool) {
119
+ if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || "") || excludedNames && excludedNames.has(tool.name)) return;
120
+ var annotations = tool.annotations || {};
121
+ descriptors.push({
122
+ name: tool.name,
123
+ title: tool.title || null,
124
+ description: tool.description,
125
+ inputSchema: tool.inputSchema === void 0 ? null : JSON.stringify(tool.inputSchema),
126
+ readOnly: annotations.readOnlyHint === true,
127
+ consequential: annotations.consequentialHint === true,
128
+ untrustedContent: annotations.untrustedContentHint === true
129
+ });
130
+ });
131
+ return descriptors;
132
+ }
133
+ /**
134
+ * Runs a page tool the chat asked for with executeTool, and answers what the hub expects back: `{ text, isError }`.
135
+ *
136
+ * @param {any} modelContext The page's ModelContext.
137
+ * @param {string} name The tool's name.
138
+ * @param {string} argumentsJson The tool's input as JSON.
139
+ * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to refuse.
140
+ */
141
+ async function runPageTool(modelContext, name, argumentsJson, options) {
142
+ var input;
143
+ try {
144
+ input = argumentsJson ? JSON.parse(argumentsJson) : {};
145
+ } catch (error) {
146
+ return {
147
+ text: "The tool input is not valid JSON.",
148
+ isError: true
149
+ };
150
+ }
151
+ if (!input || typeof input !== "object" || Array.isArray(input)) return {
152
+ text: "The tool input must be a JSON object.",
153
+ isError: true
154
+ };
155
+ var excludedNames = options && options.excludedNames;
156
+ if (excludedNames && excludedNames.has(name)) return {
157
+ text: "The tool \"" + name + "\" is not a page tool.",
158
+ isError: true
159
+ };
160
+ try {
161
+ var tool = (await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {})).find(function(candidate) {
162
+ return candidate.name === name;
163
+ });
164
+ if (!tool) return {
165
+ text: "The page has no tool named \"" + name + "\".",
166
+ isError: true
167
+ };
168
+ var serialized = await modelContext.executeTool(tool, input);
169
+ var text = serialized;
170
+ try {
171
+ var value = JSON.parse(serialized);
172
+ if (typeof value === "string") text = value;
173
+ } catch (error) {}
174
+ return {
175
+ text: text == null ? "" : String(text),
176
+ isError: false
177
+ };
178
+ } catch (error) {
179
+ return {
180
+ text: "The tool failed: " + (error && error.message ? error.message : String(error)),
181
+ isError: true
182
+ };
183
+ }
184
+ }
185
+ /**
186
+ * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection
187
+ * and waits for the result; `sync` sends the page's tools before a message, only when they changed.
188
+ *
189
+ * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.
190
+ * @param {any} connection The SignalR hub connection of the chat.
191
+ * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to leave out.
192
+ */
193
+ function connectChat(modelContext, connection, options) {
194
+ if (!modelContext) return {
195
+ sync: function() {
196
+ return Promise.resolve(0);
197
+ },
198
+ reset: function() {}
199
+ };
200
+ var settings = options || {};
201
+ /** @type {string | null} */
202
+ var lastSignature = null;
203
+ connection.on("ExecutePageTool", function(name, argumentsJson) {
204
+ return runPageTool(modelContext, name, argumentsJson, settings);
205
+ });
206
+ if (typeof connection.onreconnected === "function") connection.onreconnected(function() {
207
+ lastSignature = null;
208
+ });
209
+ async function sync() {
210
+ var tools;
211
+ try {
212
+ tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});
213
+ } catch (error) {
214
+ console.warn("The page's WebMCP tools could not be read.", error);
215
+ return 0;
216
+ }
217
+ var descriptors = toPageToolDescriptors(tools, settings.excludedNames);
218
+ var signature = JSON.stringify(descriptors);
219
+ if (signature === lastSignature) return descriptors.length;
220
+ if (descriptors.length === 0 && lastSignature === null) {
221
+ lastSignature = signature;
222
+ return 0;
223
+ }
224
+ try {
225
+ if (descriptors.length === 0) await connection.invoke("ClearPageTools");
226
+ else await connection.invoke("SetPageTools", descriptors);
227
+ lastSignature = signature;
228
+ } catch (error) {
229
+ console.warn("The page tools could not be sent to the chat.", error);
230
+ }
231
+ return descriptors.length;
232
+ }
233
+ return {
234
+ sync,
235
+ reset: function() {
236
+ lastSignature = null;
237
+ }
238
+ };
239
+ }
240
+ function init(win) {
241
+ if (win.crestAppsWebMcp) return win.crestAppsWebMcp;
242
+ var config = readConfig(win.document) || {};
243
+ var modelContext = getModelContext(win.document);
244
+ /** @type {Set<string>} */
245
+ var serverToolNames = /* @__PURE__ */ new Set();
246
+ var api = {
247
+ config,
248
+ ready: modelContext && config.toolsUrl && typeof win.fetch === "function" ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function(names) {
249
+ names.forEach(function(name) {
250
+ serverToolNames.add(name);
251
+ });
252
+ return names;
253
+ }) : Promise.resolve([]),
254
+ isSupported: function() {
255
+ return !!modelContext;
256
+ },
257
+ connectChat: function(connection) {
258
+ return connectChat(modelContext, connection, {
259
+ fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : void 0,
260
+ excludedNames: serverToolNames
261
+ });
262
+ }
263
+ };
264
+ win.crestAppsWebMcp = api;
265
+ if (win.document && typeof win.document.dispatchEvent === "function" && typeof win.CustomEvent === "function") win.document.dispatchEvent(new win.CustomEvent("crestapps-webmcp-ready", { detail: api }));
266
+ return api;
267
+ }
268
+ window.CrestAppsWebMcp = {
269
+ getModelContext,
270
+ readConfig,
271
+ createServerTool,
272
+ registerServerTools,
273
+ toPageToolDescriptors,
274
+ runPageTool,
275
+ connectChat,
276
+ init
277
+ };
278
+ if (window.document) init(window);
279
+ })();
280
+ //#endregion
281
+ })();
282
+ //# sourceMappingURL=webmcp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.js","sources":["../../Assets/js/webmcp.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP for CrestApps.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) lets a web page give tools to AI agents through\n * `document.modelContext`. This script uses only that standard API:\n *\n * - It registers the AI tools the site lets the signed-in user call, each as a standard tool whose `execute` posts to\n * the site with the user's antiforgery token. Any agent that reads the page's tools, such as the browser's own\n * assistant, can find and run them.\n * - It lets the CrestApps AI chat use every tool the page exposes, whoever registered it: `getTools()` lists them,\n * and `executeTool()` runs the one the model calls. Only the site tools it registered itself are left out, because\n * the chat reaches those on the server.\n *\n * Load webmcp-polyfill.js first, so browsers without native WebMCP get the reference implementation; a browser that\n * has WebMCP keeps its own. The page configuration is read from a\n * `<script type=\"application/json\" id=\"crestapps-webmcp-config\">` block: `toolsUrl` and `invokeUrlTemplate` (with a\n * `{name}` placeholder) for the site's tools, `antiforgeryHeaderName` and `antiforgeryToken` for the calls,\n * `pageToolOrigins` for the cross-origin frames whose tools the chat may use, and any setting other page scripts need.\n */\n(function () {\n 'use strict';\n\n var CONFIG_ELEMENT_ID = 'crestapps-webmcp-config';\n var MAX_PAGE_TOOLS = 64;\n var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;\n\n /**\n * Finds the page's WebMCP ModelContext.\n *\n * @param {any} doc The document.\n */\n function getModelContext(doc) {\n return doc && doc.modelContext ? doc.modelContext : null;\n }\n\n /**\n * Reads the configuration block the site writes into the page.\n *\n * @param {any} doc The document.\n */\n function readConfig(doc) {\n var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;\n\n if (!element) {\n return null;\n }\n\n try {\n return JSON.parse(element.textContent);\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Creates the standard tool that calls one of the site's AI tools.\n *\n * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.\n * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n */\n function createServerTool(descriptor, config, fetchImpl) {\n if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) {\n throw new TypeError('A site tool needs a name and an address to call.');\n }\n\n var url = config.invokeUrlTemplate.replace('{name}', encodeURIComponent(descriptor.name));\n /** @type {any} */\n var tool = {\n name: descriptor.name,\n description: descriptor.description,\n // The site's tools read data the page did not write, so agents are told to treat what they answer as data.\n annotations: { untrustedContentHint: true },\n execute: async function (input) {\n /** @type {Record<string, string>} */\n var headers = { 'Content-Type': 'application/json' };\n\n if (config.antiforgeryHeaderName && config.antiforgeryToken) {\n headers[config.antiforgeryHeaderName] = config.antiforgeryToken;\n }\n\n var response = await fetchImpl(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: headers,\n body: JSON.stringify(input || {}),\n });\n\n if (!response.ok) {\n throw new Error('The site refused the tool call (' + response.status + ').');\n }\n\n var data = await response.json();\n var text = (Array.isArray(data.content) ? data.content : [])\n .filter(function (part) {\n return part && part.type === 'text';\n })\n .map(function (part) {\n return part.text == null ? '' : String(part.text);\n })\n .join('\\n');\n\n if (data.isError) {\n // A rejected execute is how WebMCP reports a failed tool.\n throw new Error(text || 'The tool failed.');\n }\n\n return text;\n },\n };\n\n if (descriptor.title) {\n tool.title = descriptor.title;\n }\n\n if (descriptor.inputSchema) {\n tool.inputSchema = descriptor.inputSchema;\n }\n\n return tool;\n }\n\n /**\n * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {any} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n * @returns {Promise<string[]>} The names of the tools registered.\n */\n async function registerServerTools(modelContext, config, fetchImpl, signal) {\n if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) {\n return [];\n }\n\n var descriptors;\n\n try {\n var response = await fetchImpl(config.toolsUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } });\n\n if (!response.ok) {\n return [];\n }\n\n var data = await response.json();\n\n descriptors = data && Array.isArray(data.tools) ? data.tools : [];\n } catch (error) {\n console.warn('The site\\'s WebMCP tools could not be loaded.', error);\n\n return [];\n }\n\n /** @type {string[]} */\n var names = [];\n\n for (var i = 0; i < descriptors.length; i++) {\n try {\n await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal: signal } : {});\n names.push(descriptors[i].name);\n } catch (error) {\n console.warn('A site WebMCP tool could not be registered.', error);\n }\n }\n\n return names;\n }\n\n /**\n * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with\n * their schemas as JSON text and their annotations as flags.\n *\n * @param {Array<any>} tools The RegisteredTool records getTools() answered.\n * @param {Set<string>} [excludedNames] The names to leave out.\n */\n function toPageToolDescriptors(tools, excludedNames) {\n /** @type {Array<any>} */\n var descriptors = [];\n\n (tools || []).forEach(function (tool) {\n if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || '') || (excludedNames && excludedNames.has(tool.name))) {\n return;\n }\n\n var annotations = tool.annotations || {};\n\n descriptors.push({\n name: tool.name,\n title: tool.title || null,\n description: tool.description,\n inputSchema: tool.inputSchema === undefined ? null : JSON.stringify(tool.inputSchema),\n readOnly: annotations.readOnlyHint === true,\n consequential: annotations.consequentialHint === true,\n untrustedContent: annotations.untrustedContentHint === true,\n });\n });\n\n return descriptors;\n }\n\n /**\n * Runs a page tool the chat asked for with executeTool, and answers what the hub expects back: `{ text, isError }`.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {string} name The tool's name.\n * @param {string} argumentsJson The tool's input as JSON.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to refuse.\n */\n async function runPageTool(modelContext, name, argumentsJson, options) {\n var input;\n\n try {\n input = argumentsJson ? JSON.parse(argumentsJson) : {};\n } catch (error) {\n return { text: 'The tool input is not valid JSON.', isError: true };\n }\n\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return { text: 'The tool input must be a JSON object.', isError: true };\n }\n\n var excludedNames = options && options.excludedNames;\n\n if (excludedNames && excludedNames.has(name)) {\n return { text: 'The tool \"' + name + '\" is not a page tool.', isError: true };\n }\n\n try {\n var tools = await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {});\n var tool = tools.find(function (candidate) {\n return candidate.name === name;\n });\n\n if (!tool) {\n return { text: 'The page has no tool named \"' + name + '\".', isError: true };\n }\n\n var serialized = await modelContext.executeTool(tool, input);\n var text = serialized;\n\n try {\n var value = JSON.parse(serialized);\n\n if (typeof value === 'string') {\n text = value;\n }\n } catch (error) {\n // The result is not JSON; keep it as it is.\n }\n\n return { text: text == null ? '' : String(text), isError: false };\n } catch (error) {\n return { text: 'The tool failed: ' + (error && error.message ? error.message : String(error)), isError: true };\n }\n }\n\n /**\n * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection\n * and waits for the result; `sync` sends the page's tools before a message, only when they changed.\n *\n * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.\n * @param {any} connection The SignalR hub connection of the chat.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to leave out.\n */\n function connectChat(modelContext, connection, options) {\n if (!modelContext) {\n return {\n sync: function () {\n return Promise.resolve(0);\n },\n reset: function () { },\n };\n }\n\n var settings = options || {};\n /** @type {string | null} */\n var lastSignature = null;\n\n connection.on('ExecutePageTool', function (name, argumentsJson) {\n return runPageTool(modelContext, name, argumentsJson, settings);\n });\n\n if (typeof connection.onreconnected === 'function') {\n // A reconnected connection has a new identifier, and the hub forgot the tools of the old one.\n connection.onreconnected(function () {\n lastSignature = null;\n });\n }\n\n async function sync() {\n var tools;\n\n try {\n tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});\n } catch (error) {\n console.warn('The page\\'s WebMCP tools could not be read.', error);\n\n return 0;\n }\n\n var descriptors = toPageToolDescriptors(tools, settings.excludedNames);\n var signature = JSON.stringify(descriptors);\n\n if (signature === lastSignature) {\n return descriptors.length;\n }\n\n if (descriptors.length === 0 && lastSignature === null) {\n lastSignature = signature;\n\n return 0;\n }\n\n try {\n if (descriptors.length === 0) {\n await connection.invoke('ClearPageTools');\n } else {\n await connection.invoke('SetPageTools', descriptors);\n }\n\n lastSignature = signature;\n } catch (error) {\n console.warn('The page tools could not be sent to the chat.', error);\n }\n\n return descriptors.length;\n }\n\n return {\n sync: sync,\n reset: function () {\n lastSignature = null;\n },\n };\n }\n\n function init(win) {\n if (win.crestAppsWebMcp) {\n return win.crestAppsWebMcp;\n }\n\n var config = readConfig(win.document) || {};\n var modelContext = getModelContext(win.document);\n /** @type {Set<string>} */\n var serverToolNames = new Set();\n var ready = modelContext && config.toolsUrl && typeof win.fetch === 'function'\n ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function (names) {\n names.forEach(function (name) {\n serverToolNames.add(name);\n });\n\n return names;\n })\n : Promise.resolve([]);\n\n var api = {\n config: config,\n ready: ready,\n isSupported: function () {\n return !!modelContext;\n },\n connectChat: function (connection) {\n return connectChat(modelContext, connection, {\n fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : undefined,\n excludedNames: serverToolNames,\n });\n },\n };\n\n win.crestAppsWebMcp = api;\n\n if (win.document && typeof win.document.dispatchEvent === 'function' && typeof win.CustomEvent === 'function') {\n win.document.dispatchEvent(new win.CustomEvent('crestapps-webmcp-ready', { detail: api }));\n }\n\n return api;\n }\n\n window.CrestAppsWebMcp = {\n getModelContext: getModelContext,\n readConfig: readConfig,\n createServerTool: createServerTool,\n registerServerTools: registerServerTools,\n toPageToolDescriptors: toPageToolDescriptors,\n runPageTool: runPageTool,\n connectChat: connectChat,\n init: init,\n };\n\n if (window.document) {\n init(window);\n }\n})();\n"],"names":[],"mappings":";;;;;;CAoBA,CAAC,WAAY;EACX;EAEA,IAAI,oBAAoB;EACxB,IAAI,iBAAiB;EACrB,IAAI,YAAY;;;;;;EAOhB,SAAS,gBAAgB,KAAK;GAC5B,OAAO,OAAO,IAAI,eAAe,IAAI,eAAe;EACtD;;;;;;EAOA,SAAS,WAAW,KAAK;GACvB,IAAI,UAAU,OAAO,IAAI,iBAAiB,IAAI,eAAe,iBAAiB,IAAI;GAElF,IAAI,CAAC,SACH,OAAO;GAGT,IAAI;IACF,OAAO,KAAK,MAAM,QAAQ,WAAW;GACvC,SAAS,OAAO;IACd,OAAO;GACT;EACF;;;;;;;;EASA,SAAS,iBAAiB,YAAY,QAAQ,WAAW;GACvD,IAAI,CAAC,cAAc,CAAC,WAAW,QAAQ,CAAC,UAAU,CAAC,OAAO,mBACxD,MAAM,IAAI,UAAU,kDAAkD;GAGxE,IAAI,MAAM,OAAO,kBAAkB,QAAQ,UAAU,mBAAmB,WAAW,IAAI,CAAC;;GAExF,IAAI,OAAO;IACT,MAAM,WAAW;IACjB,aAAa,WAAW;IAExB,aAAa,EAAE,sBAAsB,KAAK;IAC1C,SAAS,eAAgB,OAAO;;KAE9B,IAAI,UAAU,EAAE,gBAAgB,mBAAmB;KAEnD,IAAI,OAAO,yBAAyB,OAAO,kBACzC,QAAQ,OAAO,yBAAyB,OAAO;KAGjD,IAAI,WAAW,MAAM,UAAU,KAAK;MAClC,QAAQ;MACR,aAAa;MACJ;MACT,MAAM,KAAK,UAAU,SAAS,CAAC,CAAC;KAClC,CAAC;KAED,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,MAAM,qCAAqC,SAAS,SAAS,IAAI;KAG7E,IAAI,OAAO,MAAM,SAAS,KAAK;KAC/B,IAAI,QAAQ,MAAM,QAAQ,KAAK,OAAO,IAAI,KAAK,UAAU,CAAC,EAAA,CACvD,OAAO,SAAU,MAAM;MACtB,OAAO,QAAQ,KAAK,SAAS;KAC/B,CAAC,CAAC,CACD,IAAI,SAAU,MAAM;MACnB,OAAO,KAAK,QAAQ,OAAO,KAAK,OAAO,KAAK,IAAI;KAClD,CAAC,CAAC,CACD,KAAK,IAAI;KAEZ,IAAI,KAAK,SAEP,MAAM,IAAI,MAAM,QAAQ,kBAAkB;KAG5C,OAAO;IACT;GACF;GAEA,IAAI,WAAW,OACb,KAAK,QAAQ,WAAW;GAG1B,IAAI,WAAW,aACb,KAAK,cAAc,WAAW;GAGhC,OAAO;EACT;;;;;;;;;;EAWA,eAAe,oBAAoB,cAAc,QAAQ,WAAW,QAAQ;GAC1E,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,OAAO,YAAY,CAAC,OAAO,mBAC1D,OAAO,CAAC;GAGV,IAAI;GAEJ,IAAI;IACF,IAAI,WAAW,MAAM,UAAU,OAAO,UAAU;KAAE,aAAa;KAAe,SAAS,EAAE,QAAQ,mBAAmB;IAAE,CAAC;IAEvH,IAAI,CAAC,SAAS,IACZ,OAAO,CAAC;IAGV,IAAI,OAAO,MAAM,SAAS,KAAK;IAE/B,cAAc,QAAQ,MAAM,QAAQ,KAAK,KAAK,IAAI,KAAK,QAAQ,CAAC;GAClE,SAAS,OAAO;IACd,QAAQ,KAAK,gDAAiD,KAAK;IAEnE,OAAO,CAAC;GACV;;GAGA,IAAI,QAAQ,CAAC;GAEb,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KACtC,IAAI;IACF,MAAM,aAAa,aAAa,iBAAiB,YAAY,IAAI,QAAQ,SAAS,GAAG,SAAS,EAAU,OAAO,IAAI,CAAC,CAAC;IACrH,MAAM,KAAK,YAAY,EAAE,CAAC,IAAI;GAChC,SAAS,OAAO;IACd,QAAQ,KAAK,+CAA+C,KAAK;GACnE;GAGF,OAAO;EACT;;;;;;;;EASA,SAAS,sBAAsB,OAAO,eAAe;;GAEnD,IAAI,cAAc,CAAC;GAEnB,CAAC,SAAS,CAAC,EAAA,CAAG,QAAQ,SAAU,MAAM;IACpC,IAAI,YAAY,UAAU,kBAAkB,CAAC,QAAQ,CAAC,UAAU,KAAK,KAAK,QAAQ,EAAE,KAAM,iBAAiB,cAAc,IAAI,KAAK,IAAI,GACpI;IAGF,IAAI,cAAc,KAAK,eAAe,CAAC;IAEvC,YAAY,KAAK;KACf,MAAM,KAAK;KACX,OAAO,KAAK,SAAS;KACrB,aAAa,KAAK;KAClB,aAAa,KAAK,gBAAgB,SAAY,OAAO,KAAK,UAAU,KAAK,WAAW;KACpF,UAAU,YAAY,iBAAiB;KACvC,eAAe,YAAY,sBAAsB;KACjD,kBAAkB,YAAY,yBAAyB;IACzD,CAAC;GACH,CAAC;GAED,OAAO;EACT;;;;;;;;;EAUA,eAAe,YAAY,cAAc,MAAM,eAAe,SAAS;GACrE,IAAI;GAEJ,IAAI;IACF,QAAQ,gBAAgB,KAAK,MAAM,aAAa,IAAI,CAAC;GACvD,SAAS,OAAO;IACd,OAAO;KAAE,MAAM;KAAqC,SAAS;IAAK;GACpE;GAEA,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAC5D,OAAO;IAAE,MAAM;IAAyC,SAAS;GAAK;GAGxE,IAAI,gBAAgB,WAAW,QAAQ;GAEvC,IAAI,iBAAiB,cAAc,IAAI,IAAI,GACzC,OAAO;IAAE,MAAM,gBAAe,OAAO;IAAyB,SAAS;GAAK;GAG9E,IAAI;IAEF,IAAI,QAAO,MADO,aAAa,SAAS,WAAW,QAAQ,cAAc,EAAE,aAAa,QAAQ,YAAY,IAAI,CAAC,CAAC,EACvG,CAAM,KAAK,SAAU,WAAW;KACzC,OAAO,UAAU,SAAS;IAC5B,CAAC;IAED,IAAI,CAAC,MACH,OAAO;KAAE,MAAM,kCAAiC,OAAO;KAAM,SAAS;IAAK;IAG7E,IAAI,aAAa,MAAM,aAAa,YAAY,MAAM,KAAK;IAC3D,IAAI,OAAO;IAEX,IAAI;KACF,IAAI,QAAQ,KAAK,MAAM,UAAU;KAEjC,IAAI,OAAO,UAAU,UACnB,OAAO;IAEX,SAAS,OAAO,CAEhB;IAEA,OAAO;KAAE,MAAM,QAAQ,OAAO,KAAK,OAAO,IAAI;KAAG,SAAS;IAAM;GAClE,SAAS,OAAO;IACd,OAAO;KAAE,MAAM,uBAAuB,SAAS,MAAM,UAAU,MAAM,UAAU,OAAO,KAAK;KAAI,SAAS;IAAK;GAC/G;EACF;;;;;;;;;EAUA,SAAS,YAAY,cAAc,YAAY,SAAS;GACtD,IAAI,CAAC,cACH,OAAO;IACL,MAAM,WAAY;KAChB,OAAO,QAAQ,QAAQ,CAAC;IAC1B;IACA,OAAO,WAAY,CAAE;GACvB;GAGF,IAAI,WAAW,WAAW,CAAC;;GAE3B,IAAI,gBAAgB;GAEpB,WAAW,GAAG,mBAAmB,SAAU,MAAM,eAAe;IAC9D,OAAO,YAAY,cAAc,MAAM,eAAe,QAAQ;GAChE,CAAC;GAED,IAAI,OAAO,WAAW,kBAAkB,YAEtC,WAAW,cAAc,WAAY;IACnC,gBAAgB;GAClB,CAAC;GAGH,eAAe,OAAO;IACpB,IAAI;IAEJ,IAAI;KACF,QAAQ,MAAM,aAAa,SAAS,SAAS,cAAc,EAAE,aAAa,SAAS,YAAY,IAAI,CAAC,CAAC;IACvG,SAAS,OAAO;KACd,QAAQ,KAAK,8CAA+C,KAAK;KAEjE,OAAO;IACT;IAEA,IAAI,cAAc,sBAAsB,OAAO,SAAS,aAAa;IACrE,IAAI,YAAY,KAAK,UAAU,WAAW;IAE1C,IAAI,cAAc,eAChB,OAAO,YAAY;IAGrB,IAAI,YAAY,WAAW,KAAK,kBAAkB,MAAM;KACtD,gBAAgB;KAEhB,OAAO;IACT;IAEA,IAAI;KACF,IAAI,YAAY,WAAW,GACzB,MAAM,WAAW,OAAO,gBAAgB;UAExC,MAAM,WAAW,OAAO,gBAAgB,WAAW;KAGrD,gBAAgB;IAClB,SAAS,OAAO;KACd,QAAQ,KAAK,iDAAiD,KAAK;IACrE;IAEA,OAAO,YAAY;GACrB;GAEA,OAAO;IACC;IACN,OAAO,WAAY;KACjB,gBAAgB;IAClB;GACF;EACF;EAEA,SAAS,KAAK,KAAK;GACjB,IAAI,IAAI,iBACN,OAAO,IAAI;GAGb,IAAI,SAAS,WAAW,IAAI,QAAQ,KAAK,CAAC;GAC1C,IAAI,eAAe,gBAAgB,IAAI,QAAQ;;GAE/C,IAAI,kCAAkB,IAAI,IAAI;GAW9B,IAAI,MAAM;IACA;IACD,OAZG,gBAAgB,OAAO,YAAY,OAAO,IAAI,UAAU,aAChE,oBAAoB,cAAc,QAAQ,IAAI,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,KAAK,SAAU,OAAO;KACrF,MAAM,QAAQ,SAAU,MAAM;MAC5B,gBAAgB,IAAI,IAAI;KAC1B,CAAC;KAED,OAAO;IACT,CAAC,IACC,QAAQ,QAAQ,CAAC,CAAC;IAKpB,aAAa,WAAY;KACvB,OAAO,CAAC,CAAC;IACX;IACA,aAAa,SAAU,YAAY;KACjC,OAAO,YAAY,cAAc,YAAY;MAC3C,aAAa,MAAM,QAAQ,OAAO,eAAe,IAAI,OAAO,kBAAkB;MAC9E,eAAe;KACjB,CAAC;IACH;GACF;GAEA,IAAI,kBAAkB;GAEtB,IAAI,IAAI,YAAY,OAAO,IAAI,SAAS,kBAAkB,cAAc,OAAO,IAAI,gBAAgB,YACjG,IAAI,SAAS,cAAc,IAAI,IAAI,YAAY,0BAA0B,EAAE,QAAQ,IAAI,CAAC,CAAC;GAG3F,OAAO;EACT;EAEA,OAAO,kBAAkB;GACN;GACL;GACM;GACG;GACE;GACV;GACA;GACP;EACR;EAEA,IAAI,OAAO,UACT,KAAK,MAAM;CAEf,EAAA,CAAG"}
@@ -0,0 +1,3 @@
1
+ (function(){(function(){"use strict";var e=/^[A-Za-z0-9_.-]{1,128}$/;function t(e){return e&&e.modelContext?e.modelContext:null}function n(e){var t=e&&e.getElementById?e.getElementById(`crestapps-webmcp-config`):null;if(!t)return null;try{return JSON.parse(t.textContent)}catch{return null}}function r(e,t,n){if(!e||!e.name||!t||!t.invokeUrlTemplate)throw TypeError(`A site tool needs a name and an address to call.`);var r=t.invokeUrlTemplate.replace(`{name}`,encodeURIComponent(e.name)),i={name:e.name,description:e.description,annotations:{untrustedContentHint:!0},execute:async function(e){var i={"Content-Type":`application/json`};t.antiforgeryHeaderName&&t.antiforgeryToken&&(i[t.antiforgeryHeaderName]=t.antiforgeryToken);var a=await n(r,{method:`POST`,credentials:`same-origin`,headers:i,body:JSON.stringify(e||{})});if(!a.ok)throw Error(`The site refused the tool call (`+a.status+`).`);var o=await a.json(),s=(Array.isArray(o.content)?o.content:[]).filter(function(e){return e&&e.type===`text`}).map(function(e){return e.text==null?``:String(e.text)}).join(`
2
+ `);if(o.isError)throw Error(s||`The tool failed.`);return s}};return e.title&&(i.title=e.title),e.inputSchema&&(i.inputSchema=e.inputSchema),i}async function i(e,t,n,i){if(!e||!t||!t.toolsUrl||!t.invokeUrlTemplate)return[];var a;try{var o=await n(t.toolsUrl,{credentials:`same-origin`,headers:{Accept:`application/json`}});if(!o.ok)return[];var s=await o.json();a=s&&Array.isArray(s.tools)?s.tools:[]}catch(e){return console.warn(`The site's WebMCP tools could not be loaded.`,e),[]}for(var c=[],l=0;l<a.length;l++)try{await e.registerTool(r(a[l],t,n),i?{signal:i}:{}),c.push(a[l].name)}catch(e){console.warn(`A site WebMCP tool could not be registered.`,e)}return c}function a(t,n){var r=[];return(t||[]).forEach(function(t){if(!(r.length>=64||!t||!e.test(t.name||``)||n&&n.has(t.name))){var i=t.annotations||{};r.push({name:t.name,title:t.title||null,description:t.description,inputSchema:t.inputSchema===void 0?null:JSON.stringify(t.inputSchema),readOnly:i.readOnlyHint===!0,consequential:i.consequentialHint===!0,untrustedContent:i.untrustedContentHint===!0})}}),r}async function o(e,t,n,r){var i;try{i=n?JSON.parse(n):{}}catch{return{text:`The tool input is not valid JSON.`,isError:!0}}if(!i||typeof i!=`object`||Array.isArray(i))return{text:`The tool input must be a JSON object.`,isError:!0};var a=r&&r.excludedNames;if(a&&a.has(t))return{text:`The tool "`+t+`" is not a page tool.`,isError:!0};try{var o=(await e.getTools(r&&r.fromOrigins?{fromOrigins:r.fromOrigins}:{})).find(function(e){return e.name===t});if(!o)return{text:`The page has no tool named "`+t+`".`,isError:!0};var s=await e.executeTool(o,i),c=s;try{var l=JSON.parse(s);typeof l==`string`&&(c=l)}catch{}return{text:c==null?``:String(c),isError:!1}}catch(e){return{text:`The tool failed: `+(e&&e.message?e.message:String(e)),isError:!0}}}function s(e,t,n){if(!e)return{sync:function(){return Promise.resolve(0)},reset:function(){}};var r=n||{},i=null;t.on(`ExecutePageTool`,function(t,n){return o(e,t,n,r)}),typeof t.onreconnected==`function`&&t.onreconnected(function(){i=null});async function s(){var n;try{n=await e.getTools(r.fromOrigins?{fromOrigins:r.fromOrigins}:{})}catch(e){return console.warn(`The page's WebMCP tools could not be read.`,e),0}var o=a(n,r.excludedNames),s=JSON.stringify(o);if(s===i)return o.length;if(o.length===0&&i===null)return i=s,0;try{o.length===0?await t.invoke(`ClearPageTools`):await t.invoke(`SetPageTools`,o),i=s}catch(e){console.warn(`The page tools could not be sent to the chat.`,e)}return o.length}return{sync:s,reset:function(){i=null}}}function c(e){if(e.crestAppsWebMcp)return e.crestAppsWebMcp;var r=n(e.document)||{},a=t(e.document),o=new Set,c={config:r,ready:a&&r.toolsUrl&&typeof e.fetch==`function`?i(a,r,e.fetch.bind(e)).then(function(e){return e.forEach(function(e){o.add(e)}),e}):Promise.resolve([]),isSupported:function(){return!!a},connectChat:function(e){return s(a,e,{fromOrigins:Array.isArray(r.pageToolOrigins)?r.pageToolOrigins:void 0,excludedNames:o})}};return e.crestAppsWebMcp=c,e.document&&typeof e.document.dispatchEvent==`function`&&typeof e.CustomEvent==`function`&&e.document.dispatchEvent(new e.CustomEvent(`crestapps-webmcp-ready`,{detail:c})),c}window.CrestAppsWebMcp={getModelContext:t,readConfig:n,createServerTool:r,registerServerTools:i,toPageToolDescriptors:a,runPageTool:o,connectChat:s,init:c},window.document&&c(window)})()})();
3
+ //# sourceMappingURL=webmcp.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webmcp.min.js","sources":["../../Assets/js/webmcp.js"],"sourcesContent":["// @ts-check\n/*\n * WebMCP for CrestApps.\n *\n * WebMCP (https://webmachinelearning.github.io/webmcp/) lets a web page give tools to AI agents through\n * `document.modelContext`. This script uses only that standard API:\n *\n * - It registers the AI tools the site lets the signed-in user call, each as a standard tool whose `execute` posts to\n * the site with the user's antiforgery token. Any agent that reads the page's tools, such as the browser's own\n * assistant, can find and run them.\n * - It lets the CrestApps AI chat use every tool the page exposes, whoever registered it: `getTools()` lists them,\n * and `executeTool()` runs the one the model calls. Only the site tools it registered itself are left out, because\n * the chat reaches those on the server.\n *\n * Load webmcp-polyfill.js first, so browsers without native WebMCP get the reference implementation; a browser that\n * has WebMCP keeps its own. The page configuration is read from a\n * `<script type=\"application/json\" id=\"crestapps-webmcp-config\">` block: `toolsUrl` and `invokeUrlTemplate` (with a\n * `{name}` placeholder) for the site's tools, `antiforgeryHeaderName` and `antiforgeryToken` for the calls,\n * `pageToolOrigins` for the cross-origin frames whose tools the chat may use, and any setting other page scripts need.\n */\n(function () {\n 'use strict';\n\n var CONFIG_ELEMENT_ID = 'crestapps-webmcp-config';\n var MAX_PAGE_TOOLS = 64;\n var TOOL_NAME = /^[A-Za-z0-9_.-]{1,128}$/;\n\n /**\n * Finds the page's WebMCP ModelContext.\n *\n * @param {any} doc The document.\n */\n function getModelContext(doc) {\n return doc && doc.modelContext ? doc.modelContext : null;\n }\n\n /**\n * Reads the configuration block the site writes into the page.\n *\n * @param {any} doc The document.\n */\n function readConfig(doc) {\n var element = doc && doc.getElementById ? doc.getElementById(CONFIG_ELEMENT_ID) : null;\n\n if (!element) {\n return null;\n }\n\n try {\n return JSON.parse(element.textContent);\n } catch (error) {\n return null;\n }\n }\n\n /**\n * Creates the standard tool that calls one of the site's AI tools.\n *\n * @param {{ name: string, title?: string, description: string, inputSchema?: any }} descriptor The tool the site lists.\n * @param {{ invokeUrlTemplate?: string, antiforgeryHeaderName?: string, antiforgeryToken?: string }} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n */\n function createServerTool(descriptor, config, fetchImpl) {\n if (!descriptor || !descriptor.name || !config || !config.invokeUrlTemplate) {\n throw new TypeError('A site tool needs a name and an address to call.');\n }\n\n var url = config.invokeUrlTemplate.replace('{name}', encodeURIComponent(descriptor.name));\n /** @type {any} */\n var tool = {\n name: descriptor.name,\n description: descriptor.description,\n // The site's tools read data the page did not write, so agents are told to treat what they answer as data.\n annotations: { untrustedContentHint: true },\n execute: async function (input) {\n /** @type {Record<string, string>} */\n var headers = { 'Content-Type': 'application/json' };\n\n if (config.antiforgeryHeaderName && config.antiforgeryToken) {\n headers[config.antiforgeryHeaderName] = config.antiforgeryToken;\n }\n\n var response = await fetchImpl(url, {\n method: 'POST',\n credentials: 'same-origin',\n headers: headers,\n body: JSON.stringify(input || {}),\n });\n\n if (!response.ok) {\n throw new Error('The site refused the tool call (' + response.status + ').');\n }\n\n var data = await response.json();\n var text = (Array.isArray(data.content) ? data.content : [])\n .filter(function (part) {\n return part && part.type === 'text';\n })\n .map(function (part) {\n return part.text == null ? '' : String(part.text);\n })\n .join('\\n');\n\n if (data.isError) {\n // A rejected execute is how WebMCP reports a failed tool.\n throw new Error(text || 'The tool failed.');\n }\n\n return text;\n },\n };\n\n if (descriptor.title) {\n tool.title = descriptor.title;\n }\n\n if (descriptor.inputSchema) {\n tool.inputSchema = descriptor.inputSchema;\n }\n\n return tool;\n }\n\n /**\n * Fetches the AI tools the site lets this user call and registers each with the page's ModelContext.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {any} config The page configuration.\n * @param {Function} fetchImpl The fetch function.\n * @param {AbortSignal} [signal] Unregisters the tools when aborted.\n * @returns {Promise<string[]>} The names of the tools registered.\n */\n async function registerServerTools(modelContext, config, fetchImpl, signal) {\n if (!modelContext || !config || !config.toolsUrl || !config.invokeUrlTemplate) {\n return [];\n }\n\n var descriptors;\n\n try {\n var response = await fetchImpl(config.toolsUrl, { credentials: 'same-origin', headers: { Accept: 'application/json' } });\n\n if (!response.ok) {\n return [];\n }\n\n var data = await response.json();\n\n descriptors = data && Array.isArray(data.tools) ? data.tools : [];\n } catch (error) {\n console.warn('The site\\'s WebMCP tools could not be loaded.', error);\n\n return [];\n }\n\n /** @type {string[]} */\n var names = [];\n\n for (var i = 0; i < descriptors.length; i++) {\n try {\n await modelContext.registerTool(createServerTool(descriptors[i], config, fetchImpl), signal ? { signal: signal } : {});\n names.push(descriptors[i].name);\n } catch (error) {\n console.warn('A site WebMCP tool could not be registered.', error);\n }\n }\n\n return names;\n }\n\n /**\n * Turns the page's registered tools into what the chat hub accepts: tools with valid WebMCP names, at most 64, with\n * their schemas as JSON text and their annotations as flags.\n *\n * @param {Array<any>} tools The RegisteredTool records getTools() answered.\n * @param {Set<string>} [excludedNames] The names to leave out.\n */\n function toPageToolDescriptors(tools, excludedNames) {\n /** @type {Array<any>} */\n var descriptors = [];\n\n (tools || []).forEach(function (tool) {\n if (descriptors.length >= MAX_PAGE_TOOLS || !tool || !TOOL_NAME.test(tool.name || '') || (excludedNames && excludedNames.has(tool.name))) {\n return;\n }\n\n var annotations = tool.annotations || {};\n\n descriptors.push({\n name: tool.name,\n title: tool.title || null,\n description: tool.description,\n inputSchema: tool.inputSchema === undefined ? null : JSON.stringify(tool.inputSchema),\n readOnly: annotations.readOnlyHint === true,\n consequential: annotations.consequentialHint === true,\n untrustedContent: annotations.untrustedContentHint === true,\n });\n });\n\n return descriptors;\n }\n\n /**\n * Runs a page tool the chat asked for with executeTool, and answers what the hub expects back: `{ text, isError }`.\n *\n * @param {any} modelContext The page's ModelContext.\n * @param {string} name The tool's name.\n * @param {string} argumentsJson The tool's input as JSON.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to refuse.\n */\n async function runPageTool(modelContext, name, argumentsJson, options) {\n var input;\n\n try {\n input = argumentsJson ? JSON.parse(argumentsJson) : {};\n } catch (error) {\n return { text: 'The tool input is not valid JSON.', isError: true };\n }\n\n if (!input || typeof input !== 'object' || Array.isArray(input)) {\n return { text: 'The tool input must be a JSON object.', isError: true };\n }\n\n var excludedNames = options && options.excludedNames;\n\n if (excludedNames && excludedNames.has(name)) {\n return { text: 'The tool \"' + name + '\" is not a page tool.', isError: true };\n }\n\n try {\n var tools = await modelContext.getTools(options && options.fromOrigins ? { fromOrigins: options.fromOrigins } : {});\n var tool = tools.find(function (candidate) {\n return candidate.name === name;\n });\n\n if (!tool) {\n return { text: 'The page has no tool named \"' + name + '\".', isError: true };\n }\n\n var serialized = await modelContext.executeTool(tool, input);\n var text = serialized;\n\n try {\n var value = JSON.parse(serialized);\n\n if (typeof value === 'string') {\n text = value;\n }\n } catch (error) {\n // The result is not JSON; keep it as it is.\n }\n\n return { text: text == null ? '' : String(text), isError: false };\n } catch (error) {\n return { text: 'The tool failed: ' + (error && error.message ? error.message : String(error)), isError: true };\n }\n }\n\n /**\n * Connects the page's tools to a CrestApps chat hub connection. The hub calls `ExecutePageTool` on the connection\n * and waits for the result; `sync` sends the page's tools before a message, only when they changed.\n *\n * @param {any} modelContext The page's ModelContext, or null when the page has no WebMCP.\n * @param {any} connection The SignalR hub connection of the chat.\n * @param {{ fromOrigins?: string[], excludedNames?: Set<string> }} [options] The frames to look in and the names to leave out.\n */\n function connectChat(modelContext, connection, options) {\n if (!modelContext) {\n return {\n sync: function () {\n return Promise.resolve(0);\n },\n reset: function () { },\n };\n }\n\n var settings = options || {};\n /** @type {string | null} */\n var lastSignature = null;\n\n connection.on('ExecutePageTool', function (name, argumentsJson) {\n return runPageTool(modelContext, name, argumentsJson, settings);\n });\n\n if (typeof connection.onreconnected === 'function') {\n // A reconnected connection has a new identifier, and the hub forgot the tools of the old one.\n connection.onreconnected(function () {\n lastSignature = null;\n });\n }\n\n async function sync() {\n var tools;\n\n try {\n tools = await modelContext.getTools(settings.fromOrigins ? { fromOrigins: settings.fromOrigins } : {});\n } catch (error) {\n console.warn('The page\\'s WebMCP tools could not be read.', error);\n\n return 0;\n }\n\n var descriptors = toPageToolDescriptors(tools, settings.excludedNames);\n var signature = JSON.stringify(descriptors);\n\n if (signature === lastSignature) {\n return descriptors.length;\n }\n\n if (descriptors.length === 0 && lastSignature === null) {\n lastSignature = signature;\n\n return 0;\n }\n\n try {\n if (descriptors.length === 0) {\n await connection.invoke('ClearPageTools');\n } else {\n await connection.invoke('SetPageTools', descriptors);\n }\n\n lastSignature = signature;\n } catch (error) {\n console.warn('The page tools could not be sent to the chat.', error);\n }\n\n return descriptors.length;\n }\n\n return {\n sync: sync,\n reset: function () {\n lastSignature = null;\n },\n };\n }\n\n function init(win) {\n if (win.crestAppsWebMcp) {\n return win.crestAppsWebMcp;\n }\n\n var config = readConfig(win.document) || {};\n var modelContext = getModelContext(win.document);\n /** @type {Set<string>} */\n var serverToolNames = new Set();\n var ready = modelContext && config.toolsUrl && typeof win.fetch === 'function'\n ? registerServerTools(modelContext, config, win.fetch.bind(win)).then(function (names) {\n names.forEach(function (name) {\n serverToolNames.add(name);\n });\n\n return names;\n })\n : Promise.resolve([]);\n\n var api = {\n config: config,\n ready: ready,\n isSupported: function () {\n return !!modelContext;\n },\n connectChat: function (connection) {\n return connectChat(modelContext, connection, {\n fromOrigins: Array.isArray(config.pageToolOrigins) ? config.pageToolOrigins : undefined,\n excludedNames: serverToolNames,\n });\n },\n };\n\n win.crestAppsWebMcp = api;\n\n if (win.document && typeof win.document.dispatchEvent === 'function' && typeof win.CustomEvent === 'function') {\n win.document.dispatchEvent(new win.CustomEvent('crestapps-webmcp-ready', { detail: api }));\n }\n\n return api;\n }\n\n window.CrestAppsWebMcp = {\n getModelContext: getModelContext,\n readConfig: readConfig,\n createServerTool: createServerTool,\n registerServerTools: registerServerTools,\n toPageToolDescriptors: toPageToolDescriptors,\n runPageTool: runPageTool,\n connectChat: connectChat,\n init: init,\n };\n\n if (window.document) {\n init(window);\n }\n})();\n"],"names":["TOOL_NAME","getModelContext","doc","readConfig","element","createServerTool","descriptor","config","url","tool","headers","response","fetchImpl","input","data","text","part","registerServerTools","modelContext","descriptors","error","names","i","signal","toPageToolDescriptors","tools","excludedNames","annotations","runPageTool","argumentsJson","options","name","candidate","serialized","value","connectChat","settings","lastSignature","connection","sync","signature","init","win","serverToolNames","api"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,48 @@
1
1
  {
2
2
  "name": "@crestapps/webmcp",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.0.0-preview.246",
4
+ "description": "WebMCP for CrestApps: registers a site's AI tools and page tools with the standard document.modelContext API, and lets the CrestApps AI chat discover and run any WebMCP tool a page exposes. Ships the W3C Web Machine Learning Community Group polyfill for browsers without native WebMCP.",
5
+ "license": "MIT",
6
+ "author": "CrestApps",
7
+ "homepage": "https://crestapps.com/docs/core/mcp/webmcp",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/CrestApps/CrestApps.Core.git",
11
+ "directory": "src/Resources/CrestApps.AI.WebMcp.Resources"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/CrestApps/CrestApps.Core/issues"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public",
18
+ "registry": "https://registry.npmjs.org/"
19
+ },
20
+ "keywords": [
21
+ "ai",
22
+ "webmcp",
23
+ "mcp",
24
+ "model-context-protocol",
25
+ "modelcontext",
26
+ "browser-agents",
27
+ "crestapps"
28
+ ],
29
+ "files": [
30
+ "dist/"
31
+ ],
32
+ "main": "dist/webmcp.js",
33
+ "exports": {
34
+ ".": "./dist/webmcp.js",
35
+ "./webmcp": "./dist/webmcp.js",
36
+ "./webmcp.min": "./dist/webmcp.min.js",
37
+ "./webmcp-page-tools": "./dist/webmcp-page-tools.js",
38
+ "./webmcp-page-tools.min": "./dist/webmcp-page-tools.min.js",
39
+ "./webmcp-polyfill": "./dist/webmcp-polyfill.js",
40
+ "./dist/*": "./dist/*"
41
+ },
42
+ "scripts": {
43
+ "prepublishOnly": "node scripts/prepare-dist.js"
44
+ },
45
+ "devDependencies": {
46
+ "@mcp-b/webmcp-polyfill": "6.0.3"
47
+ }
48
+ }
package/README.md DELETED
@@ -1,3 +0,0 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.