auto-webmcp 0.3.16 → 0.3.18
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 +308 -8
- package/dist/auto-webmcp.cjs.js.map +2 -2
- package/dist/auto-webmcp.esm.js +308 -8
- 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 +48 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery.d.ts.map +1 -1
- package/dist/interceptor.d.ts +12 -2
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/registry.d.ts +1 -0
- package/dist/registry.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,10 +26,21 @@ 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 ?? {},
|
|
43
|
+
preserveExisting: userConfig?.preserveExisting ?? false,
|
|
33
44
|
debug: userConfig?.debug ?? false
|
|
34
45
|
};
|
|
35
46
|
}
|
|
@@ -989,6 +1000,53 @@ function buildSchemaFromInputs(inputs) {
|
|
|
989
1000
|
}
|
|
990
1001
|
|
|
991
1002
|
// src/registry.ts
|
|
1003
|
+
var EXECUTE_OUTPUT_SCHEMA = {
|
|
1004
|
+
type: "object",
|
|
1005
|
+
properties: {
|
|
1006
|
+
status: {
|
|
1007
|
+
type: "string",
|
|
1008
|
+
enum: ["success", "partial", "error", "awaiting_user_action", "timed_out", "blocked_invalid"],
|
|
1009
|
+
description: "Outcome of the form execution."
|
|
1010
|
+
},
|
|
1011
|
+
filled_fields: {
|
|
1012
|
+
type: "object",
|
|
1013
|
+
description: "Field name to submitted value map."
|
|
1014
|
+
},
|
|
1015
|
+
skipped_fields: {
|
|
1016
|
+
type: "array",
|
|
1017
|
+
items: { type: "string" },
|
|
1018
|
+
description: "Fields the agent provided but that could not be filled."
|
|
1019
|
+
},
|
|
1020
|
+
missing_required: {
|
|
1021
|
+
type: "array",
|
|
1022
|
+
items: { type: "string" },
|
|
1023
|
+
description: "Required fields not supplied by the agent."
|
|
1024
|
+
},
|
|
1025
|
+
validation_errors: {
|
|
1026
|
+
type: "array",
|
|
1027
|
+
items: {
|
|
1028
|
+
type: "object",
|
|
1029
|
+
properties: {
|
|
1030
|
+
field: { type: "string" },
|
|
1031
|
+
constraint: { type: "string", description: "HTML ValidityState key that failed." },
|
|
1032
|
+
message: { type: "string" }
|
|
1033
|
+
},
|
|
1034
|
+
required: ["field", "constraint", "message"]
|
|
1035
|
+
},
|
|
1036
|
+
description: "Per-field HTML5 validation failures (present when status is blocked_invalid)."
|
|
1037
|
+
},
|
|
1038
|
+
existing_values: {
|
|
1039
|
+
type: "object",
|
|
1040
|
+
description: "Field values present in the form before the agent filled it."
|
|
1041
|
+
},
|
|
1042
|
+
warnings: {
|
|
1043
|
+
type: "array",
|
|
1044
|
+
items: { type: "object" },
|
|
1045
|
+
description: "Non-fatal fill warnings (alias_resolved, clamped, not_filled, etc.)."
|
|
1046
|
+
}
|
|
1047
|
+
},
|
|
1048
|
+
required: ["status", "filled_fields", "skipped_fields", "missing_required", "warnings"]
|
|
1049
|
+
};
|
|
992
1050
|
var registeredTools = /* @__PURE__ */ new Map();
|
|
993
1051
|
var registrationControllers = /* @__PURE__ */ new Map();
|
|
994
1052
|
function isWebMCPSupported() {
|
|
@@ -1005,6 +1063,7 @@ async function registerFormTool(form, metadata, execute) {
|
|
|
1005
1063
|
name: metadata.name,
|
|
1006
1064
|
description: metadata.description,
|
|
1007
1065
|
inputSchema: metadata.inputSchema,
|
|
1066
|
+
outputSchema: EXECUTE_OUTPUT_SCHEMA,
|
|
1008
1067
|
execute
|
|
1009
1068
|
};
|
|
1010
1069
|
if (metadata.annotations && Object.keys(metadata.annotations).length > 0) {
|
|
@@ -1058,9 +1117,128 @@ var formFieldElements = /* @__PURE__ */ new WeakMap();
|
|
|
1058
1117
|
var pendingWarnings = /* @__PURE__ */ new WeakMap();
|
|
1059
1118
|
var pendingFillWarnings = /* @__PURE__ */ new WeakMap();
|
|
1060
1119
|
var lastFilledSnapshot = /* @__PURE__ */ new WeakMap();
|
|
1120
|
+
var preFillValues = /* @__PURE__ */ new WeakMap();
|
|
1061
1121
|
var _inputValueSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")?.set;
|
|
1062
1122
|
var _textareaValueSetter = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")?.set;
|
|
1063
1123
|
var _checkedSetter = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "checked")?.set;
|
|
1124
|
+
function normalizeAliasKey(raw) {
|
|
1125
|
+
return raw.toLowerCase().replace(/[^a-z0-9]+/g, "");
|
|
1126
|
+
}
|
|
1127
|
+
function addAlias(index, alias, schemaKey) {
|
|
1128
|
+
if (!alias)
|
|
1129
|
+
return;
|
|
1130
|
+
const normalized = normalizeAliasKey(alias);
|
|
1131
|
+
if (!normalized)
|
|
1132
|
+
return;
|
|
1133
|
+
if (!index.has(normalized))
|
|
1134
|
+
index.set(normalized, /* @__PURE__ */ new Set());
|
|
1135
|
+
index.get(normalized).add(schemaKey);
|
|
1136
|
+
}
|
|
1137
|
+
function buildAliasIndex(form, metadata) {
|
|
1138
|
+
const index = /* @__PURE__ */ new Map();
|
|
1139
|
+
const properties = metadata?.inputSchema?.properties ?? {};
|
|
1140
|
+
for (const [schemaKey, prop] of Object.entries(properties)) {
|
|
1141
|
+
addAlias(index, schemaKey, schemaKey);
|
|
1142
|
+
addAlias(index, schemaKey.replace(/_/g, " "), schemaKey);
|
|
1143
|
+
addAlias(index, prop.title, schemaKey);
|
|
1144
|
+
const nativeEl = findNativeField(form, schemaKey);
|
|
1145
|
+
const mappedEl = metadata?.fieldElements?.get(schemaKey);
|
|
1146
|
+
const el = nativeEl ?? mappedEl ?? null;
|
|
1147
|
+
if (!el)
|
|
1148
|
+
continue;
|
|
1149
|
+
const htmlEl = el;
|
|
1150
|
+
addAlias(index, htmlEl.getAttribute("id"), schemaKey);
|
|
1151
|
+
addAlias(index, htmlEl.getAttribute("name"), schemaKey);
|
|
1152
|
+
addAlias(index, htmlEl.getAttribute("aria-label"), schemaKey);
|
|
1153
|
+
addAlias(index, htmlEl.getAttribute("placeholder"), schemaKey);
|
|
1154
|
+
if (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement) {
|
|
1155
|
+
for (const label of Array.from(el.labels ?? [])) {
|
|
1156
|
+
addAlias(index, label.textContent?.trim(), schemaKey);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
return index;
|
|
1161
|
+
}
|
|
1162
|
+
function resolveParamsForSchema(form, params, metadata, config) {
|
|
1163
|
+
const resolved = {};
|
|
1164
|
+
const warnings = [];
|
|
1165
|
+
const properties = metadata?.inputSchema?.properties ?? {};
|
|
1166
|
+
const aliasEnabled = config.paramBinding.enableAliasResolution;
|
|
1167
|
+
for (const [key, value] of Object.entries(params)) {
|
|
1168
|
+
if (key in properties)
|
|
1169
|
+
resolved[key] = value;
|
|
1170
|
+
}
|
|
1171
|
+
if (!aliasEnabled)
|
|
1172
|
+
return { resolved, warnings };
|
|
1173
|
+
const aliasIndex = buildAliasIndex(form, metadata);
|
|
1174
|
+
for (const [rawKey, value] of Object.entries(params)) {
|
|
1175
|
+
if (rawKey in properties)
|
|
1176
|
+
continue;
|
|
1177
|
+
const candidates = aliasIndex.get(normalizeAliasKey(rawKey));
|
|
1178
|
+
if (!candidates || candidates.size !== 1)
|
|
1179
|
+
continue;
|
|
1180
|
+
const target = Array.from(candidates)[0];
|
|
1181
|
+
if (!target || target in resolved)
|
|
1182
|
+
continue;
|
|
1183
|
+
resolved[target] = value;
|
|
1184
|
+
warnings.push({
|
|
1185
|
+
field: target,
|
|
1186
|
+
type: "alias_resolved",
|
|
1187
|
+
original: rawKey,
|
|
1188
|
+
message: `resolved "${rawKey}" to schema field "${target}"`
|
|
1189
|
+
});
|
|
1190
|
+
}
|
|
1191
|
+
return { resolved, warnings };
|
|
1192
|
+
}
|
|
1193
|
+
function collectInvalidFieldWarnings(form) {
|
|
1194
|
+
const warnings = [];
|
|
1195
|
+
const controls = Array.from(form.elements).filter(
|
|
1196
|
+
(el) => el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement || el instanceof HTMLSelectElement
|
|
1197
|
+
);
|
|
1198
|
+
for (const control of controls) {
|
|
1199
|
+
if (!control.willValidate)
|
|
1200
|
+
continue;
|
|
1201
|
+
if (control.checkValidity())
|
|
1202
|
+
continue;
|
|
1203
|
+
const field = control.name || control.id || control.getAttribute("aria-label") || "unknown_field";
|
|
1204
|
+
warnings.push({
|
|
1205
|
+
field,
|
|
1206
|
+
type: "blocked_submit",
|
|
1207
|
+
message: control.validationMessage || `field "${field}" failed validation`
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
return warnings;
|
|
1211
|
+
}
|
|
1212
|
+
function captureCurrentValues(form) {
|
|
1213
|
+
const result = {};
|
|
1214
|
+
try {
|
|
1215
|
+
const data = new FormData(form);
|
|
1216
|
+
for (const [key, val] of data.entries()) {
|
|
1217
|
+
if (result[key] !== void 0) {
|
|
1218
|
+
const existing = result[key];
|
|
1219
|
+
result[key] = Array.isArray(existing) ? [...existing, val] : [existing, val];
|
|
1220
|
+
} else {
|
|
1221
|
+
result[key] = val;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
} catch {
|
|
1225
|
+
}
|
|
1226
|
+
return result;
|
|
1227
|
+
}
|
|
1228
|
+
function collectValidationErrors(form) {
|
|
1229
|
+
const errors = [];
|
|
1230
|
+
for (const control of Array.from(form.elements)) {
|
|
1231
|
+
if (!(control instanceof HTMLInputElement) && !(control instanceof HTMLTextAreaElement) && !(control instanceof HTMLSelectElement))
|
|
1232
|
+
continue;
|
|
1233
|
+
if (!control.willValidate || control.checkValidity())
|
|
1234
|
+
continue;
|
|
1235
|
+
const field = control.name || control.id || control.getAttribute("aria-label") || "unknown_field";
|
|
1236
|
+
const v = control.validity;
|
|
1237
|
+
const constraint = v.valueMissing ? "valueMissing" : v.typeMismatch ? "typeMismatch" : v.patternMismatch ? "patternMismatch" : v.tooLong ? "tooLong" : v.tooShort ? "tooShort" : v.rangeUnderflow ? "rangeUnderflow" : v.rangeOverflow ? "rangeOverflow" : v.stepMismatch ? "stepMismatch" : v.customError ? "customError" : "badInput";
|
|
1238
|
+
errors.push({ field, constraint, message: control.validationMessage || `field "${field}" failed validation` });
|
|
1239
|
+
}
|
|
1240
|
+
return errors;
|
|
1241
|
+
}
|
|
1064
1242
|
function buildExecuteHandler(form, config, toolName, metadata) {
|
|
1065
1243
|
if (metadata?.fieldElements) {
|
|
1066
1244
|
formFieldElements.set(form, metadata.fieldElements);
|
|
@@ -1082,22 +1260,86 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1082
1260
|
}
|
|
1083
1261
|
pendingFillWarnings.set(form, []);
|
|
1084
1262
|
pendingWarnings.delete(form);
|
|
1085
|
-
|
|
1086
|
-
|
|
1263
|
+
const existingSnapshot = captureCurrentValues(form);
|
|
1264
|
+
preFillValues.set(form, existingSnapshot);
|
|
1265
|
+
const { resolved: resolvedParams, warnings: aliasWarnings } = resolveParamsForSchema(
|
|
1266
|
+
form,
|
|
1267
|
+
params,
|
|
1268
|
+
metadata,
|
|
1269
|
+
config
|
|
1270
|
+
);
|
|
1271
|
+
if (aliasWarnings.length > 0) {
|
|
1272
|
+
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...aliasWarnings]);
|
|
1273
|
+
}
|
|
1274
|
+
let paramsToFill = resolvedParams;
|
|
1275
|
+
if (config.preserveExisting) {
|
|
1276
|
+
const preserved = [];
|
|
1277
|
+
paramsToFill = Object.fromEntries(
|
|
1278
|
+
Object.entries(resolvedParams).filter(([key]) => {
|
|
1279
|
+
const current = existingSnapshot[key];
|
|
1280
|
+
const hasValue = current !== void 0 && current !== "" && current !== null;
|
|
1281
|
+
if (hasValue) {
|
|
1282
|
+
preserved.push({
|
|
1283
|
+
field: key,
|
|
1284
|
+
type: "not_filled",
|
|
1285
|
+
message: `field "${key}" already has a value and preserveExisting is enabled`
|
|
1286
|
+
});
|
|
1287
|
+
}
|
|
1288
|
+
return !hasValue;
|
|
1289
|
+
})
|
|
1290
|
+
);
|
|
1291
|
+
if (preserved.length > 0) {
|
|
1292
|
+
pendingFillWarnings.set(form, [...pendingFillWarnings.get(form) ?? [], ...preserved]);
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
fillFormFields(form, paramsToFill);
|
|
1296
|
+
const missingNow = getMissingRequired(metadata, resolvedParams);
|
|
1087
1297
|
if (missingNow.length > 0)
|
|
1088
1298
|
pendingWarnings.set(form, missingNow);
|
|
1089
1299
|
window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
|
|
1090
1300
|
return new Promise((resolve, reject) => {
|
|
1091
|
-
|
|
1301
|
+
const timeoutMs = config.execution.timeoutMs;
|
|
1302
|
+
const timeoutId = setTimeout(() => {
|
|
1303
|
+
const pending = pendingExecutions.get(form);
|
|
1304
|
+
if (!pending)
|
|
1305
|
+
return;
|
|
1306
|
+
pendingExecutions.delete(form);
|
|
1307
|
+
const timedOutState = config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0 ? "timed_out" : "awaiting_user_action";
|
|
1308
|
+
const warn = {
|
|
1309
|
+
field: "__form__",
|
|
1310
|
+
type: "timeout",
|
|
1311
|
+
message: timedOutState === "timed_out" ? `tool execution timed out after ${timeoutMs}ms` : `waiting for user submit (timed out after ${timeoutMs}ms)`
|
|
1312
|
+
};
|
|
1313
|
+
const _existingValsTimeout = preFillValues.get(form);
|
|
1314
|
+
const structured = {
|
|
1315
|
+
status: timedOutState,
|
|
1316
|
+
filled_fields: serializeFormData(form, lastParams.get(form), formFieldElements.get(form)),
|
|
1317
|
+
skipped_fields: [],
|
|
1318
|
+
missing_required: pendingWarnings.get(form) ?? [],
|
|
1319
|
+
warnings: [...pendingFillWarnings.get(form) ?? [], warn],
|
|
1320
|
+
..._existingValsTimeout !== void 0 && { existing_values: _existingValsTimeout }
|
|
1321
|
+
};
|
|
1322
|
+
pendingWarnings.delete(form);
|
|
1323
|
+
pendingFillWarnings.delete(form);
|
|
1324
|
+
lastFilledSnapshot.delete(form);
|
|
1325
|
+
preFillValues.delete(form);
|
|
1326
|
+
resolve({
|
|
1327
|
+
content: [
|
|
1328
|
+
{ type: "text", text: warn.message },
|
|
1329
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1330
|
+
]
|
|
1331
|
+
});
|
|
1332
|
+
}, timeoutMs);
|
|
1333
|
+
pendingExecutions.set(form, { resolve, reject, timeoutId });
|
|
1092
1334
|
if (config.autoSubmit || form.hasAttribute("toolautosubmit") || form.dataset["webmcpAutosubmit"] !== void 0) {
|
|
1093
1335
|
waitForDomStable(form).then(async () => {
|
|
1094
1336
|
try {
|
|
1095
|
-
fillFormFields(form,
|
|
1337
|
+
fillFormFields(form, resolvedParams);
|
|
1096
1338
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1097
|
-
const reset = getResetFields(form,
|
|
1339
|
+
const reset = getResetFields(form, resolvedParams, formFieldElements.get(form));
|
|
1098
1340
|
if (reset.length === 0)
|
|
1099
1341
|
break;
|
|
1100
|
-
fillFormFields(form,
|
|
1342
|
+
fillFormFields(form, resolvedParams);
|
|
1101
1343
|
await waitForDomStable(form, 400, 100);
|
|
1102
1344
|
}
|
|
1103
1345
|
let submitForm = form;
|
|
@@ -1108,7 +1350,9 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1108
1350
|
const found = liveBtn?.closest("form");
|
|
1109
1351
|
if (found) {
|
|
1110
1352
|
submitForm = found;
|
|
1111
|
-
pendingExecutions.
|
|
1353
|
+
const pending = pendingExecutions.get(form);
|
|
1354
|
+
const nextPending = pending?.timeoutId ? { resolve, reject, timeoutId: pending.timeoutId } : { resolve, reject };
|
|
1355
|
+
pendingExecutions.set(submitForm, nextPending);
|
|
1112
1356
|
attachSubmitInterceptor(submitForm, toolName);
|
|
1113
1357
|
}
|
|
1114
1358
|
}
|
|
@@ -1116,6 +1360,43 @@ function buildExecuteHandler(form, config, toolName, metadata) {
|
|
|
1116
1360
|
pendingWarnings.set(submitForm, pendingWarnings.get(form));
|
|
1117
1361
|
pendingWarnings.delete(form);
|
|
1118
1362
|
}
|
|
1363
|
+
if (!submitForm.checkValidity()) {
|
|
1364
|
+
const pending = pendingExecutions.get(submitForm) ?? pendingExecutions.get(form);
|
|
1365
|
+
if (pending) {
|
|
1366
|
+
if (pending.timeoutId)
|
|
1367
|
+
clearTimeout(pending.timeoutId);
|
|
1368
|
+
pendingExecutions.delete(submitForm);
|
|
1369
|
+
pendingExecutions.delete(form);
|
|
1370
|
+
const warnings = [
|
|
1371
|
+
...pendingFillWarnings.get(submitForm) ?? pendingFillWarnings.get(form) ?? [],
|
|
1372
|
+
...collectInvalidFieldWarnings(submitForm)
|
|
1373
|
+
];
|
|
1374
|
+
pendingFillWarnings.delete(submitForm);
|
|
1375
|
+
pendingFillWarnings.delete(form);
|
|
1376
|
+
const _existingValsBlocked = preFillValues.get(form);
|
|
1377
|
+
const structured = {
|
|
1378
|
+
status: "blocked_invalid",
|
|
1379
|
+
filled_fields: serializeFormData(submitForm, lastParams.get(submitForm) ?? lastParams.get(form), formFieldElements.get(submitForm) ?? formFieldElements.get(form)),
|
|
1380
|
+
skipped_fields: [],
|
|
1381
|
+
missing_required: pendingWarnings.get(submitForm) ?? pendingWarnings.get(form) ?? [],
|
|
1382
|
+
warnings,
|
|
1383
|
+
validation_errors: collectValidationErrors(submitForm),
|
|
1384
|
+
..._existingValsBlocked !== void 0 && { existing_values: _existingValsBlocked }
|
|
1385
|
+
};
|
|
1386
|
+
pendingWarnings.delete(submitForm);
|
|
1387
|
+
pendingWarnings.delete(form);
|
|
1388
|
+
lastFilledSnapshot.delete(submitForm);
|
|
1389
|
+
lastFilledSnapshot.delete(form);
|
|
1390
|
+
preFillValues.delete(form);
|
|
1391
|
+
resolve({
|
|
1392
|
+
content: [
|
|
1393
|
+
{ type: "text", text: "Form submission blocked by native validation." },
|
|
1394
|
+
{ type: "text", text: JSON.stringify(structured) }
|
|
1395
|
+
]
|
|
1396
|
+
});
|
|
1397
|
+
}
|
|
1398
|
+
return;
|
|
1399
|
+
}
|
|
1119
1400
|
submitForm.requestSubmit();
|
|
1120
1401
|
} catch (err) {
|
|
1121
1402
|
reject(err instanceof Error ? err : new Error(String(err)));
|
|
@@ -1134,9 +1415,13 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1134
1415
|
if (!pending)
|
|
1135
1416
|
return;
|
|
1136
1417
|
const { resolve } = pending;
|
|
1418
|
+
if (pending.timeoutId)
|
|
1419
|
+
clearTimeout(pending.timeoutId);
|
|
1137
1420
|
pendingExecutions.delete(form);
|
|
1138
1421
|
const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
|
|
1422
|
+
const existingVals = preFillValues.get(form);
|
|
1139
1423
|
lastFilledSnapshot.delete(form);
|
|
1424
|
+
preFillValues.delete(form);
|
|
1140
1425
|
const missingRequired = pendingWarnings.get(form) ?? [];
|
|
1141
1426
|
pendingWarnings.delete(form);
|
|
1142
1427
|
const fillWarnings = pendingFillWarnings.get(form) ?? [];
|
|
@@ -1154,7 +1439,8 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1154
1439
|
message: `required field "${f}" was not provided`
|
|
1155
1440
|
})),
|
|
1156
1441
|
...fillWarnings
|
|
1157
|
-
]
|
|
1442
|
+
],
|
|
1443
|
+
...existingVals !== void 0 && { existing_values: existingVals }
|
|
1158
1444
|
};
|
|
1159
1445
|
const allWarnMessages = [
|
|
1160
1446
|
...missingRequired.length ? [`required fields were not filled: ${missingRequired.join(", ")}`] : [],
|
|
@@ -1176,6 +1462,7 @@ function attachSubmitInterceptor(form, toolName) {
|
|
|
1176
1462
|
});
|
|
1177
1463
|
form.addEventListener("reset", () => {
|
|
1178
1464
|
lastFilledSnapshot.delete(form);
|
|
1465
|
+
preFillValues.delete(form);
|
|
1179
1466
|
window.dispatchEvent(new CustomEvent("toolcancel", { detail: { toolName } }));
|
|
1180
1467
|
});
|
|
1181
1468
|
}
|
|
@@ -1690,10 +1977,23 @@ function ensureUniqueToolName(baseName, excludeForm) {
|
|
|
1690
1977
|
}
|
|
1691
1978
|
return candidate;
|
|
1692
1979
|
}
|
|
1980
|
+
function hasNativeDeclarativeTool(form) {
|
|
1981
|
+
return form.getAttribute("toolname")?.trim().length ? true : false;
|
|
1982
|
+
}
|
|
1693
1983
|
async function registerForm(form, config) {
|
|
1694
1984
|
if (isExcluded(form, config))
|
|
1695
1985
|
return;
|
|
1696
1986
|
const previousName = getRegisteredToolName(form);
|
|
1987
|
+
if (hasNativeDeclarativeTool(form) && config.declarativeMode !== "force") {
|
|
1988
|
+
if (previousName) {
|
|
1989
|
+
await unregisterFormTool(form);
|
|
1990
|
+
}
|
|
1991
|
+
if (config.debug) {
|
|
1992
|
+
const mode = config.declarativeMode;
|
|
1993
|
+
console.log(`[auto-webmcp] Skipping imperative registration for native declarative form (mode=${mode})`);
|
|
1994
|
+
}
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1697
1997
|
let override;
|
|
1698
1998
|
for (const [selector, ovr] of Object.entries(config.overrides)) {
|
|
1699
1999
|
try {
|