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.
@@ -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
- fillFormFields(form, params);
1060
- const missingNow = getMissingRequired(metadata, params);
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
- pendingExecutions.set(form, { resolve, reject });
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, params);
1205
+ fillFormFields(form, resolvedParams);
1070
1206
  for (let attempt = 0; attempt < 2; attempt++) {
1071
- const reset = getResetFields(form, params, formFieldElements.get(form));
1207
+ const reset = getResetFields(form, resolvedParams, formFieldElements.get(form));
1072
1208
  if (reset.length === 0)
1073
1209
  break;
1074
- fillFormFields(form, params);
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.set(submitForm, { resolve, reject });
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 {