auto-webmcp 0.3.16 → 0.3.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/auto-webmcp.cjs.js +193 -7
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +193 -7
- package/dist/auto-webmcp.esm.js.map +2 -2
- package/dist/auto-webmcp.iife.js +1 -1
- package/dist/auto-webmcp.iife.js.map +3 -3
- package/dist/config.d.ts +41 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +2 -2
- package/dist/interceptor.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/auto-webmcp.esm.js
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
2
|
function resolveConfig(userConfig) {
|
|
3
|
+
const strict = userConfig?.paramBinding?.strict ?? false;
|
|
4
|
+
const enableAliasResolution = strict ? false : userConfig?.paramBinding?.enableAliasResolution ?? true;
|
|
3
5
|
return {
|
|
4
6
|
exclude: userConfig?.exclude ?? [],
|
|
5
7
|
autoSubmit: userConfig?.autoSubmit ?? false,
|
|
8
|
+
declarativeMode: userConfig?.declarativeMode ?? "skip",
|
|
9
|
+
paramBinding: {
|
|
10
|
+
strict,
|
|
11
|
+
enableAliasResolution
|
|
12
|
+
},
|
|
13
|
+
execution: {
|
|
14
|
+
timeoutMs: Math.max(100, userConfig?.execution?.timeoutMs ?? 15e3)
|
|
15
|
+
},
|
|
6
16
|
overrides: userConfig?.overrides ?? {},
|
|
7
17
|
debug: userConfig?.debug ?? false
|
|
8
18
|
};
|
|
@@ -1035,6 +1045,94 @@ var lastFilledSnapshot = /* @__PURE__ */ new WeakMap();
|
|
|
1035
1045
|
var _inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
1036
1046
|
var _textareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
1037
1047
|
var _checkedSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
|
|
1048
|
+
function normalizeAliasKey(raw) {
|
|
1049
|
+
return raw.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1050
|
+
}
|
|
1051
|
+
function addAlias(index, alias, schemaKey) {
|
|
1052
|
+
if (!alias)
|
|
1053
|
+
return;
|
|
1054
|
+
const normalized = normalizeAliasKey(alias);
|
|
1055
|
+
if (!normalized)
|
|
1056
|
+
return;
|
|
1057
|
+
if (!index.has(normalized))
|
|
1058
|
+
index.set(normalized, /* @__PURE__ */ new Set());
|
|
1059
|
+
index.get(normalized).add(schemaKey);
|
|
1060
|
+
}
|
|
1061
|
+
function buildAliasIndex(form, metadata) {
|
|
1062
|
+
const index = /* @__PURE__ */ new Map();
|
|
1063
|
+
const properties = metadata?.inputSchema?.properties ?? {};
|
|
1064
|
+
for (const [schemaKey, prop] of Object.entries(properties)) {
|
|
1065
|
+
addAlias(index, schemaKey, schemaKey);
|
|
1066
|
+
addAlias(index, schemaKey.replace(/_/g, " "), schemaKey);
|
|
1067
|
+
addAlias(index, prop.title, schemaKey);
|
|
1068
|
+
const nativeEl = findNativeField(form, schemaKey);
|
|
1069
|
+
const mappedEl = metadata?.fieldElements?.get(schemaKey);
|
|
1070
|
+
const el = nativeEl ?? mappedEl ?? null;
|
|
1071
|
+
if (!el)
|
|
1072
|
+
continue;
|
|
1073
|
+
const htmlEl = el;
|
|
1074
|
+
addAlias(index, htmlEl.getAttribute("id"), schemaKey);
|
|
1075
|
+
addAlias(index, htmlEl.getAttribute("name"), schemaKey);
|
|
1076
|
+
addAlias(index, htmlEl.getAttribute("aria-label"), schemaKey);
|
|
1077
|
+
addAlias(index, htmlEl.getAttribute("placeholder"), schemaKey);
|
|
1078
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
1079
|
+
for (const label of Array.from(el.labels ?? [])) {
|
|
1080
|
+
addAlias(index, label.textContent?.trim(), schemaKey);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
return index;
|
|
1085
|
+
}
|
|
1086
|
+
function resolveParamsForSchema(form, params, metadata, config) {
|
|
1087
|
+
const resolved = {};
|
|
1088
|
+
const warnings = [];
|
|
1089
|
+
const properties = metadata?.inputSchema?.properties ?? {};
|
|
1090
|
+
const aliasEnabled = config.paramBinding.enableAliasResolution;
|
|
1091
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1092
|
+
if (key in properties)
|
|
1093
|
+
resolved[key] = value;
|
|
1094
|
+
}
|
|
1095
|
+
if (!aliasEnabled)
|
|
1096
|
+
return { resolved, warnings };
|
|
1097
|
+
const aliasIndex = buildAliasIndex(form, metadata);
|
|
1098
|
+
for (const [rawKey, value] of Object.entries(params)) {
|
|
1099
|
+
if (rawKey in properties)
|
|
1100
|
+
continue;
|
|
1101
|
+
const candidates = aliasIndex.get(normalizeAliasKey(rawKey));
|
|
1102
|
+
if (!candidates || candidates.size !== 1)
|
|
1103
|
+
continue;
|
|
1104
|
+
const target = Array.from(candidates)[0];
|
|
1105
|
+
if (!target || target in resolved)
|
|
1106
|
+
continue;
|
|
1107
|
+
resolved[target] = value;
|
|
1108
|
+
warnings.push({
|
|
1109
|
+
field: target,
|
|
1110
|
+
type: "alias_resolved",
|
|
1111
|
+
original: rawKey,
|
|
1112
|
+
message: `resolved "${rawKey}" to schema field "${target}"`
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
return { resolved, warnings };
|
|
1116
|
+
}
|
|
1117
|
+
function collectInvalidFieldWarnings(form) {
|
|
1118
|
+
const warnings = [];
|
|
1119
|
+
const controls = Array.from(form.elements).filter(
|
|
1120
|
+
(el) => el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement
|
|
1121
|
+
);
|
|
1122
|
+
for (const control of controls) {
|
|
1123
|
+
if (!control.willValidate)
|
|
1124
|
+
continue;
|
|
1125
|
+
if (control.checkValidity())
|
|
1126
|
+
continue;
|
|
1127
|
+
const field = control.name || control.id || control.getAttribute("aria-label") || "unknown_field";
|
|
1128
|
+
warnings.push({
|
|
1129
|
+
field,
|
|
1130
|
+
type: "blocked_submit",
|
|
1131
|
+
message: control.validationMessage || `field "${field}" failed validation`
|
|
1132
|
+
});
|
|
1133
|
+
}
|
|
1134
|
+
return warnings;
|
|
1135
|
+
}
|
|
1038
1136
|
function buildExecuteHandler(form, config, toolName, metadata) {
|
|
1039
1137
|
if (metadata?.fieldElements) {
|
|
1040
1138
|
formFieldElements.set(form, metadata.fieldElements);
|
|
@@ -1056,22 +1154,60 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1056
1154
|
}
|
|
1057
1155
|
pendingFillWarnings.set(form, []);
|
|
1058
1156
|
pendingWarnings.delete(form);
|
|
1059
|
-
|
|
1060
|
-
|
|
1157
|
+
const { resolved: resolvedParams, warnings: aliasWarnings } = resolveParamsForSchema(
|
|
1158
|
+
form,
|
|
1159
|
+
params,
|
|
1160
|
+
metadata,
|
|
1161
|
+
config
|
|
1162
|
+
);
|
|
1163
|
+
if (aliasWarnings.length > 0) {
|
|
1164
|
+
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...aliasWarnings]);
|
|
1165
|
+
}
|
|
1166
|
+
fillFormFields(form, resolvedParams);
|
|
1167
|
+
const missingNow = getMissingRequired(metadata, resolvedParams);
|
|
1061
1168
|
if (missingNow.length > 0)
|
|
1062
1169
|
pendingWarnings.set(form, missingNow);
|
|
1063
1170
|
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
1064
1171
|
return new Promise((resolve, reject) => {
|
|
1065
|
-
|
|
1172
|
+
const timeoutMs = config.execution.timeoutMs;
|
|
1173
|
+
const timeoutId = setTimeout(() => {
|
|
1174
|
+
const pending = pendingExecutions.get(form);
|
|
1175
|
+
if (!pending)
|
|
1176
|
+
return;
|
|
1177
|
+
pendingExecutions.delete(form);
|
|
1178
|
+
const timedOutState = config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0 ? "timed_out" : "awaiting_user_action";
|
|
1179
|
+
const warn = {
|
|
1180
|
+
field: "__form__",
|
|
1181
|
+
type: "timeout",
|
|
1182
|
+
message: timedOutState === "timed_out" ? `tool execution timed out after ${timeoutMs}ms` : `waiting for user submit (timed out after ${timeoutMs}ms)`
|
|
1183
|
+
};
|
|
1184
|
+
const structured = {
|
|
1185
|
+
status: timedOutState,
|
|
1186
|
+
filled_fields: serializeFormData(form, lastParams.get(form), formFieldElements.get(form)),
|
|
1187
|
+
skipped_fields: [],
|
|
1188
|
+
missing_required: pendingWarnings.get(form) ?? [],
|
|
1189
|
+
warnings: [...pendingFillWarnings.get(form) ?? [], warn]
|
|
1190
|
+
};
|
|
1191
|
+
pendingWarnings.delete(form);
|
|
1192
|
+
pendingFillWarnings.delete(form);
|
|
1193
|
+
lastFilledSnapshot.delete(form);
|
|
1194
|
+
resolve({
|
|
1195
|
+
content: [
|
|
1196
|
+
{ type: "text", text: warn.message },
|
|
1197
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1198
|
+
]
|
|
1199
|
+
});
|
|
1200
|
+
}, timeoutMs);
|
|
1201
|
+
pendingExecutions.set(form, { resolve, reject, timeoutId });
|
|
1066
1202
|
if (config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0) {
|
|
1067
1203
|
waitForDomStable(form).then(async () => {
|
|
1068
1204
|
try {
|
|
1069
|
-
fillFormFields(form,
|
|
1205
|
+
fillFormFields(form, resolvedParams);
|
|
1070
1206
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1071
|
-
const reset = getResetFields(form,
|
|
1207
|
+
const reset = getResetFields(form, resolvedParams, formFieldElements.get(form));
|
|
1072
1208
|
if (reset.length === 0)
|
|
1073
1209
|
break;
|
|
1074
|
-
fillFormFields(form,
|
|
1210
|
+
fillFormFields(form, resolvedParams);
|
|
1075
1211
|
await waitForDomStable(form, 400, 100);
|
|
1076
1212
|
}
|
|
1077
1213
|
let submitForm = form;
|
|
@@ -1082,7 +1218,9 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1082
1218
|
const found = liveBtn?.closest("form");
|
|
1083
1219
|
if (found) {
|
|
1084
1220
|
submitForm = found;
|
|
1085
|
-
pendingExecutions.
|
|
1221
|
+
const pending = pendingExecutions.get(form);
|
|
1222
|
+
const nextPending = pending?.timeoutId ? { resolve, reject, timeoutId: pending.timeoutId } : { resolve, reject };
|
|
1223
|
+
pendingExecutions.set(submitForm, nextPending);
|
|
1086
1224
|
attachSubmitInterceptor(submitForm, toolName);
|
|
1087
1225
|
}
|
|
1088
1226
|
}
|
|
@@ -1090,6 +1228,39 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1090
1228
|
pendingWarnings.set(submitForm, pendingWarnings.get(form));
|
|
1091
1229
|
pendingWarnings.delete(form);
|
|
1092
1230
|
}
|
|
1231
|
+
if (!submitForm.checkValidity()) {
|
|
1232
|
+
const pending = pendingExecutions.get(submitForm) ?? pendingExecutions.get(form);
|
|
1233
|
+
if (pending) {
|
|
1234
|
+
if (pending.timeoutId)
|
|
1235
|
+
clearTimeout(pending.timeoutId);
|
|
1236
|
+
pendingExecutions.delete(submitForm);
|
|
1237
|
+
pendingExecutions.delete(form);
|
|
1238
|
+
const warnings = [
|
|
1239
|
+
...pendingFillWarnings.get(submitForm) ?? pendingFillWarnings.get(form) ?? [],
|
|
1240
|
+
...collectInvalidFieldWarnings(submitForm)
|
|
1241
|
+
];
|
|
1242
|
+
pendingFillWarnings.delete(submitForm);
|
|
1243
|
+
pendingFillWarnings.delete(form);
|
|
1244
|
+
const structured = {
|
|
1245
|
+
status: "blocked_invalid",
|
|
1246
|
+
filled_fields: serializeFormData(submitForm, lastParams.get(submitForm) ?? lastParams.get(form), formFieldElements.get(submitForm) ?? formFieldElements.get(form)),
|
|
1247
|
+
skipped_fields: [],
|
|
1248
|
+
missing_required: pendingWarnings.get(submitForm) ?? pendingWarnings.get(form) ?? [],
|
|
1249
|
+
warnings
|
|
1250
|
+
};
|
|
1251
|
+
pendingWarnings.delete(submitForm);
|
|
1252
|
+
pendingWarnings.delete(form);
|
|
1253
|
+
lastFilledSnapshot.delete(submitForm);
|
|
1254
|
+
lastFilledSnapshot.delete(form);
|
|
1255
|
+
resolve({
|
|
1256
|
+
content: [
|
|
1257
|
+
{ type: "text", text: "Form submission blocked by native validation." },
|
|
1258
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1259
|
+
]
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
return;
|
|
1263
|
+
}
|
|
1093
1264
|
submitForm.requestSubmit();
|
|
1094
1265
|
} catch (err) {
|
|
1095
1266
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1108,6 +1279,8 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1108
1279
|
if (!pending)
|
|
1109
1280
|
return;
|
|
1110
1281
|
const { resolve } = pending;
|
|
1282
|
+
if (pending.timeoutId)
|
|
1283
|
+
clearTimeout(pending.timeoutId);
|
|
1111
1284
|
pendingExecutions.delete(form);
|
|
1112
1285
|
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
1113
1286
|
lastFilledSnapshot.delete(form);
|
|
@@ -1664,10 +1837,23 @@ function ensureUniqueToolName(baseName, excludeForm) {
|
|
|
1664
1837
|
}
|
|
1665
1838
|
return candidate;
|
|
1666
1839
|
}
|
|
1840
|
+
function hasNativeDeclarativeTool(form) {
|
|
1841
|
+
return form.getAttribute("toolname")?.trim().length ? true : false;
|
|
1842
|
+
}
|
|
1667
1843
|
async function registerForm(form, config) {
|
|
1668
1844
|
if (isExcluded(form, config))
|
|
1669
1845
|
return;
|
|
1670
1846
|
const previousName = getRegisteredToolName(form);
|
|
1847
|
+
if (hasNativeDeclarativeTool(form) && config.declarativeMode !== "force") {
|
|
1848
|
+
if (previousName) {
|
|
1849
|
+
await unregisterFormTool(form);
|
|
1850
|
+
}
|
|
1851
|
+
if (config.debug) {
|
|
1852
|
+
const mode = config.declarativeMode;
|
|
1853
|
+
console.log(`[auto-webmcp] Skipping imperative registration for native declarative form (mode=${mode})`);
|
|
1854
|
+
}
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1671
1857
|
let override;
|
|
1672
1858
|
for (const [selector, ovr] of Object.entries(config.overrides)) {
|
|
1673
1859
|
try {
|