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/README.md
CHANGED
|
@@ -57,6 +57,21 @@ await autoWebMCP({
|
|
|
57
57
|
// Auto-submit when agent invokes (default: false — human must click submit)
|
|
58
58
|
autoSubmit: false,
|
|
59
59
|
|
|
60
|
+
// Handling for forms already using native declarative WebMCP attributes:
|
|
61
|
+
// 'skip' (default), 'augment' (currently same as skip), or 'force'
|
|
62
|
+
declarativeMode: 'skip',
|
|
63
|
+
|
|
64
|
+
// Parameter binding behavior for execute payload keys
|
|
65
|
+
paramBinding: {
|
|
66
|
+
enableAliasResolution: true, // default
|
|
67
|
+
strict: false, // if true, exact schema keys only
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// Deterministic execute timeout state
|
|
71
|
+
execution: {
|
|
72
|
+
timeoutMs: 15000, // default
|
|
73
|
+
},
|
|
74
|
+
|
|
60
75
|
// Per-form name / description overrides
|
|
61
76
|
overrides: {
|
|
62
77
|
'#checkout-form': {
|
package/dist/auto-webmcp.cjs.js
CHANGED
|
@@ -26,9 +26,19 @@ module.exports = __toCommonJS(src_exports);
|
|
|
26
26
|
|
|
27
27
|
// src/config.ts
|
|
28
28
|
function resolveConfig(userConfig) {
|
|
29
|
+
const strict = userConfig?.paramBinding?.strict ?? false;
|
|
30
|
+
const enableAliasResolution = strict ? false : userConfig?.paramBinding?.enableAliasResolution ?? true;
|
|
29
31
|
return {
|
|
30
32
|
exclude: userConfig?.exclude ?? [],
|
|
31
33
|
autoSubmit: userConfig?.autoSubmit ?? false,
|
|
34
|
+
declarativeMode: userConfig?.declarativeMode ?? "skip",
|
|
35
|
+
paramBinding: {
|
|
36
|
+
strict,
|
|
37
|
+
enableAliasResolution
|
|
38
|
+
},
|
|
39
|
+
execution: {
|
|
40
|
+
timeoutMs: Math.max(100, userConfig?.execution?.timeoutMs ?? 15e3)
|
|
41
|
+
},
|
|
32
42
|
overrides: userConfig?.overrides ?? {},
|
|
33
43
|
debug: userConfig?.debug ?? false
|
|
34
44
|
};
|
|
@@ -1061,6 +1071,94 @@ var lastFilledSnapshot = /* @__PURE__ */ new WeakMap();
|
|
|
1061
1071
|
var _inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
1062
1072
|
var _textareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
1063
1073
|
var _checkedSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
|
|
1074
|
+
function normalizeAliasKey(raw) {
|
|
1075
|
+
return raw.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1076
|
+
}
|
|
1077
|
+
function addAlias(index, alias, schemaKey) {
|
|
1078
|
+
if (!alias)
|
|
1079
|
+
return;
|
|
1080
|
+
const normalized = normalizeAliasKey(alias);
|
|
1081
|
+
if (!normalized)
|
|
1082
|
+
return;
|
|
1083
|
+
if (!index.has(normalized))
|
|
1084
|
+
index.set(normalized, /* @__PURE__ */ new Set());
|
|
1085
|
+
index.get(normalized).add(schemaKey);
|
|
1086
|
+
}
|
|
1087
|
+
function buildAliasIndex(form, metadata) {
|
|
1088
|
+
const index = /* @__PURE__ */ new Map();
|
|
1089
|
+
const properties = metadata?.inputSchema?.properties ?? {};
|
|
1090
|
+
for (const [schemaKey, prop] of Object.entries(properties)) {
|
|
1091
|
+
addAlias(index, schemaKey, schemaKey);
|
|
1092
|
+
addAlias(index, schemaKey.replace(/_/g, " "), schemaKey);
|
|
1093
|
+
addAlias(index, prop.title, schemaKey);
|
|
1094
|
+
const nativeEl = findNativeField(form, schemaKey);
|
|
1095
|
+
const mappedEl = metadata?.fieldElements?.get(schemaKey);
|
|
1096
|
+
const el = nativeEl ?? mappedEl ?? null;
|
|
1097
|
+
if (!el)
|
|
1098
|
+
continue;
|
|
1099
|
+
const htmlEl = el;
|
|
1100
|
+
addAlias(index, htmlEl.getAttribute("id"), schemaKey);
|
|
1101
|
+
addAlias(index, htmlEl.getAttribute("name"), schemaKey);
|
|
1102
|
+
addAlias(index, htmlEl.getAttribute("aria-label"), schemaKey);
|
|
1103
|
+
addAlias(index, htmlEl.getAttribute("placeholder"), schemaKey);
|
|
1104
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
1105
|
+
for (const label of Array.from(el.labels ?? [])) {
|
|
1106
|
+
addAlias(index, label.textContent?.trim(), schemaKey);
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return index;
|
|
1111
|
+
}
|
|
1112
|
+
function resolveParamsForSchema(form, params, metadata, config) {
|
|
1113
|
+
const resolved = {};
|
|
1114
|
+
const warnings = [];
|
|
1115
|
+
const properties = metadata?.inputSchema?.properties ?? {};
|
|
1116
|
+
const aliasEnabled = config.paramBinding.enableAliasResolution;
|
|
1117
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1118
|
+
if (key in properties)
|
|
1119
|
+
resolved[key] = value;
|
|
1120
|
+
}
|
|
1121
|
+
if (!aliasEnabled)
|
|
1122
|
+
return { resolved, warnings };
|
|
1123
|
+
const aliasIndex = buildAliasIndex(form, metadata);
|
|
1124
|
+
for (const [rawKey, value] of Object.entries(params)) {
|
|
1125
|
+
if (rawKey in properties)
|
|
1126
|
+
continue;
|
|
1127
|
+
const candidates = aliasIndex.get(normalizeAliasKey(rawKey));
|
|
1128
|
+
if (!candidates || candidates.size !== 1)
|
|
1129
|
+
continue;
|
|
1130
|
+
const target = Array.from(candidates)[0];
|
|
1131
|
+
if (!target || target in resolved)
|
|
1132
|
+
continue;
|
|
1133
|
+
resolved[target] = value;
|
|
1134
|
+
warnings.push({
|
|
1135
|
+
field: target,
|
|
1136
|
+
type: "alias_resolved",
|
|
1137
|
+
original: rawKey,
|
|
1138
|
+
message: `resolved "${rawKey}" to schema field "${target}"`
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
return { resolved, warnings };
|
|
1142
|
+
}
|
|
1143
|
+
function collectInvalidFieldWarnings(form) {
|
|
1144
|
+
const warnings = [];
|
|
1145
|
+
const controls = Array.from(form.elements).filter(
|
|
1146
|
+
(el) => el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement
|
|
1147
|
+
);
|
|
1148
|
+
for (const control of controls) {
|
|
1149
|
+
if (!control.willValidate)
|
|
1150
|
+
continue;
|
|
1151
|
+
if (control.checkValidity())
|
|
1152
|
+
continue;
|
|
1153
|
+
const field = control.name || control.id || control.getAttribute("aria-label") || "unknown_field";
|
|
1154
|
+
warnings.push({
|
|
1155
|
+
field,
|
|
1156
|
+
type: "blocked_submit",
|
|
1157
|
+
message: control.validationMessage || `field "${field}" failed validation`
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
return warnings;
|
|
1161
|
+
}
|
|
1064
1162
|
function buildExecuteHandler(form, config, toolName, metadata) {
|
|
1065
1163
|
if (metadata?.fieldElements) {
|
|
1066
1164
|
formFieldElements.set(form, metadata.fieldElements);
|
|
@@ -1082,22 +1180,60 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1082
1180
|
}
|
|
1083
1181
|
pendingFillWarnings.set(form, []);
|
|
1084
1182
|
pendingWarnings.delete(form);
|
|
1085
|
-
|
|
1086
|
-
|
|
1183
|
+
const { resolved: resolvedParams, warnings: aliasWarnings } = resolveParamsForSchema(
|
|
1184
|
+
form,
|
|
1185
|
+
params,
|
|
1186
|
+
metadata,
|
|
1187
|
+
config
|
|
1188
|
+
);
|
|
1189
|
+
if (aliasWarnings.length > 0) {
|
|
1190
|
+
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...aliasWarnings]);
|
|
1191
|
+
}
|
|
1192
|
+
fillFormFields(form, resolvedParams);
|
|
1193
|
+
const missingNow = getMissingRequired(metadata, resolvedParams);
|
|
1087
1194
|
if (missingNow.length > 0)
|
|
1088
1195
|
pendingWarnings.set(form, missingNow);
|
|
1089
1196
|
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
1090
1197
|
return new Promise((resolve, reject) => {
|
|
1091
|
-
|
|
1198
|
+
const timeoutMs = config.execution.timeoutMs;
|
|
1199
|
+
const timeoutId = setTimeout(() => {
|
|
1200
|
+
const pending = pendingExecutions.get(form);
|
|
1201
|
+
if (!pending)
|
|
1202
|
+
return;
|
|
1203
|
+
pendingExecutions.delete(form);
|
|
1204
|
+
const timedOutState = config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0 ? "timed_out" : "awaiting_user_action";
|
|
1205
|
+
const warn = {
|
|
1206
|
+
field: "__form__",
|
|
1207
|
+
type: "timeout",
|
|
1208
|
+
message: timedOutState === "timed_out" ? `tool execution timed out after ${timeoutMs}ms` : `waiting for user submit (timed out after ${timeoutMs}ms)`
|
|
1209
|
+
};
|
|
1210
|
+
const structured = {
|
|
1211
|
+
status: timedOutState,
|
|
1212
|
+
filled_fields: serializeFormData(form, lastParams.get(form), formFieldElements.get(form)),
|
|
1213
|
+
skipped_fields: [],
|
|
1214
|
+
missing_required: pendingWarnings.get(form) ?? [],
|
|
1215
|
+
warnings: [...pendingFillWarnings.get(form) ?? [], warn]
|
|
1216
|
+
};
|
|
1217
|
+
pendingWarnings.delete(form);
|
|
1218
|
+
pendingFillWarnings.delete(form);
|
|
1219
|
+
lastFilledSnapshot.delete(form);
|
|
1220
|
+
resolve({
|
|
1221
|
+
content: [
|
|
1222
|
+
{ type: "text", text: warn.message },
|
|
1223
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1224
|
+
]
|
|
1225
|
+
});
|
|
1226
|
+
}, timeoutMs);
|
|
1227
|
+
pendingExecutions.set(form, { resolve, reject, timeoutId });
|
|
1092
1228
|
if (config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0) {
|
|
1093
1229
|
waitForDomStable(form).then(async () => {
|
|
1094
1230
|
try {
|
|
1095
|
-
fillFormFields(form,
|
|
1231
|
+
fillFormFields(form, resolvedParams);
|
|
1096
1232
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1097
|
-
const reset = getResetFields(form,
|
|
1233
|
+
const reset = getResetFields(form, resolvedParams, formFieldElements.get(form));
|
|
1098
1234
|
if (reset.length === 0)
|
|
1099
1235
|
break;
|
|
1100
|
-
fillFormFields(form,
|
|
1236
|
+
fillFormFields(form, resolvedParams);
|
|
1101
1237
|
await waitForDomStable(form, 400, 100);
|
|
1102
1238
|
}
|
|
1103
1239
|
let submitForm = form;
|
|
@@ -1108,7 +1244,9 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1108
1244
|
const found = liveBtn?.closest("form");
|
|
1109
1245
|
if (found) {
|
|
1110
1246
|
submitForm = found;
|
|
1111
|
-
pendingExecutions.
|
|
1247
|
+
const pending = pendingExecutions.get(form);
|
|
1248
|
+
const nextPending = pending?.timeoutId ? { resolve, reject, timeoutId: pending.timeoutId } : { resolve, reject };
|
|
1249
|
+
pendingExecutions.set(submitForm, nextPending);
|
|
1112
1250
|
attachSubmitInterceptor(submitForm, toolName);
|
|
1113
1251
|
}
|
|
1114
1252
|
}
|
|
@@ -1116,6 +1254,39 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1116
1254
|
pendingWarnings.set(submitForm, pendingWarnings.get(form));
|
|
1117
1255
|
pendingWarnings.delete(form);
|
|
1118
1256
|
}
|
|
1257
|
+
if (!submitForm.checkValidity()) {
|
|
1258
|
+
const pending = pendingExecutions.get(submitForm) ?? pendingExecutions.get(form);
|
|
1259
|
+
if (pending) {
|
|
1260
|
+
if (pending.timeoutId)
|
|
1261
|
+
clearTimeout(pending.timeoutId);
|
|
1262
|
+
pendingExecutions.delete(submitForm);
|
|
1263
|
+
pendingExecutions.delete(form);
|
|
1264
|
+
const warnings = [
|
|
1265
|
+
...pendingFillWarnings.get(submitForm) ?? pendingFillWarnings.get(form) ?? [],
|
|
1266
|
+
...collectInvalidFieldWarnings(submitForm)
|
|
1267
|
+
];
|
|
1268
|
+
pendingFillWarnings.delete(submitForm);
|
|
1269
|
+
pendingFillWarnings.delete(form);
|
|
1270
|
+
const structured = {
|
|
1271
|
+
status: "blocked_invalid",
|
|
1272
|
+
filled_fields: serializeFormData(submitForm, lastParams.get(submitForm) ?? lastParams.get(form), formFieldElements.get(submitForm) ?? formFieldElements.get(form)),
|
|
1273
|
+
skipped_fields: [],
|
|
1274
|
+
missing_required: pendingWarnings.get(submitForm) ?? pendingWarnings.get(form) ?? [],
|
|
1275
|
+
warnings
|
|
1276
|
+
};
|
|
1277
|
+
pendingWarnings.delete(submitForm);
|
|
1278
|
+
pendingWarnings.delete(form);
|
|
1279
|
+
lastFilledSnapshot.delete(submitForm);
|
|
1280
|
+
lastFilledSnapshot.delete(form);
|
|
1281
|
+
resolve({
|
|
1282
|
+
content: [
|
|
1283
|
+
{ type: "text", text: "Form submission blocked by native validation." },
|
|
1284
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1285
|
+
]
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
return;
|
|
1289
|
+
}
|
|
1119
1290
|
submitForm.requestSubmit();
|
|
1120
1291
|
} catch (err) {
|
|
1121
1292
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1134,6 +1305,8 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1134
1305
|
if (!pending)
|
|
1135
1306
|
return;
|
|
1136
1307
|
const { resolve } = pending;
|
|
1308
|
+
if (pending.timeoutId)
|
|
1309
|
+
clearTimeout(pending.timeoutId);
|
|
1137
1310
|
pendingExecutions.delete(form);
|
|
1138
1311
|
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
1139
1312
|
lastFilledSnapshot.delete(form);
|
|
@@ -1690,10 +1863,23 @@ function ensureUniqueToolName(baseName, excludeForm) {
|
|
|
1690
1863
|
}
|
|
1691
1864
|
return candidate;
|
|
1692
1865
|
}
|
|
1866
|
+
function hasNativeDeclarativeTool(form) {
|
|
1867
|
+
return form.getAttribute("toolname")?.trim().length ? true : false;
|
|
1868
|
+
}
|
|
1693
1869
|
async function registerForm(form, config) {
|
|
1694
1870
|
if (isExcluded(form, config))
|
|
1695
1871
|
return;
|
|
1696
1872
|
const previousName = getRegisteredToolName(form);
|
|
1873
|
+
if (hasNativeDeclarativeTool(form) && config.declarativeMode !== "force") {
|
|
1874
|
+
if (previousName) {
|
|
1875
|
+
await unregisterFormTool(form);
|
|
1876
|
+
}
|
|
1877
|
+
if (config.debug) {
|
|
1878
|
+
const mode = config.declarativeMode;
|
|
1879
|
+
console.log(`[auto-webmcp] Skipping imperative registration for native declarative form (mode=${mode})`);
|
|
1880
|
+
}
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1697
1883
|
let override;
|
|
1698
1884
|
for (const [selector, ovr] of Object.entries(config.overrides)) {
|
|
1699
1885
|
try {
|