auto-webmcp 0.3.0 → 0.3.2

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 CHANGED
@@ -37,10 +37,11 @@ await autoWebMCP();
37
37
  ## What it does
38
38
 
39
39
  1. Scans the page for all `<form>` elements (on load + dynamically via `MutationObserver`)
40
- 2. Infers a meaningful **tool name**, **description**, and **JSON Schema** from the form's DOM
41
- 3. Registers each form as a WebMCP tool via `navigator.modelContext.registerTool()`
42
- 4. Intercepts submissions to return structured results back to the agent
43
- 5. Degrades silently in browsers without WebMCP
40
+ 2. Also discovers inputs **outside** `<form>` tags (orphan input groups) and inputs inside **Web Component shadow roots**
41
+ 3. Infers a meaningful **tool name**, **description**, and **JSON Schema** from the form's DOM
42
+ 4. Registers each form as a WebMCP tool via `navigator.modelContext.registerTool()`
43
+ 5. Intercepts submissions to return structured results back to the agent
44
+ 6. Degrades silently in browsers without WebMCP
44
45
 
45
46
  ---
46
47
 
@@ -50,15 +51,12 @@ await autoWebMCP();
50
51
  import { autoWebMCP } from 'auto-webmcp';
51
52
 
52
53
  await autoWebMCP({
53
- // Skip specific forms
54
+ // Skip specific forms (CSS selectors)
54
55
  exclude: ['#login-form', '[data-no-webmcp]'],
55
56
 
56
57
  // Auto-submit when agent invokes (default: false — human must click submit)
57
58
  autoSubmit: false,
58
59
 
59
- // Optional AI enrichment for richer descriptions
60
- enhance: { provider: 'claude', apiKey: 'sk-...' },
61
-
62
60
  // Per-form name / description overrides
63
61
  overrides: {
64
62
  '#checkout-form': {
@@ -74,7 +72,23 @@ await autoWebMCP({
74
72
 
75
73
  ### Per-form HTML overrides
76
74
 
77
- Override individual forms without JavaScript:
75
+ **Native WebMCP spec attributes** (highest priority):
76
+
77
+ ```html
78
+ <form
79
+ toolname="book_appointment"
80
+ tooldescription="Book a doctor appointment"
81
+ toolautosubmit
82
+ >
83
+ <input
84
+ name="date"
85
+ type="date"
86
+ toolparamdescription="Preferred appointment date"
87
+ >
88
+ </form>
89
+ ```
90
+
91
+ **data-webmcp-* attributes** (fallback, useful when you cannot edit the form element directly):
78
92
 
79
93
  ```html
80
94
  <form
@@ -88,7 +102,6 @@ Override individual forms without JavaScript:
88
102
  data-webmcp-title="Appointment Date"
89
103
  data-webmcp-description="Preferred appointment date"
90
104
  >
91
-
92
105
  </form>
93
106
  ```
94
107
 
@@ -102,36 +115,131 @@ Override individual forms without JavaScript:
102
115
 
103
116
  ## Tool name inference (priority order)
104
117
 
105
- 1. `data-webmcp-name` attribute on `<form>`
106
- 2. Submit button text (e.g. "Search Flights" → `search_flights`)
107
- 3. Nearest `<h1>`–`<h3>` heading above the form
108
- 4. Form `id` or `name` attribute
109
- 5. Last segment of form `action` URL
110
- 6. Fallback: `form_N`
118
+ 1. `toolname` native attribute on `<form>` (WebMCP spec)
119
+ 2. `data-webmcp-name` attribute on `<form>`
120
+ 3. Submit button text (e.g. "Search Flights" → `search_flights`)
121
+ 4. Nearest `<h1>`–`<h3>` heading above the form
122
+ 5. Form `id` or `name` attribute
123
+ 6. Last segment of form `action` URL
124
+ 7. Fallback: `form_N`
111
125
 
112
126
  ## Tool description inference (priority order)
113
127
 
114
- 1. `data-webmcp-description` attribute on `<form>`
115
- 2. `<legend>` text inside the form
116
- 3. `aria-label` on the form
117
- 4. `aria-describedby` target
118
- 5. Nearest heading + page `<title>`
128
+ 1. `tooldescription` native attribute on `<form>` (WebMCP spec)
129
+ 2. `data-webmcp-description` attribute on `<form>`
130
+ 3. `<legend>` text inside the form
131
+ 4. `aria-label` on the form
132
+ 5. `aria-describedby` target
133
+ 6. Nearest heading + page `<title>`
134
+
135
+ ## Field title inference (priority order)
136
+
137
+ 1. `data-webmcp-title` attribute on the field
138
+ 2. Associated `<label>` text
139
+ 3. `name` attribute (humanized)
140
+ 4. `id` attribute (humanized)
141
+ 5. `placeholder` text
142
+
143
+ ## Field description inference (priority order)
144
+
145
+ 1. `toolparamdescription` native attribute on the field (WebMCP spec)
146
+ 2. `data-webmcp-description` attribute on the field
147
+ 3. `aria-description` / `aria-describedby` target
148
+ 4. `placeholder` text
149
+
150
+ ---
119
151
 
120
152
  ## HTML → JSON Schema mapping
121
153
 
122
- | HTML type | JSON Schema |
123
- |------------------------|-------------------------|
124
- | `text`, `search`, `tel`| `string` |
125
- | `email` | `string` + format:email |
126
- | `url` | `string` + format:uri |
127
- | `number`, `range` | `number` (+ min/max) |
128
- | `date` | `string` + format:date |
129
- | `datetime-local` | `string` + format:date-time |
130
- | `checkbox` | `boolean` |
131
- | `radio` | `string` + enum |
132
- | `select` | `string` + enum |
133
- | `textarea` | `string` |
134
- | `file`, `hidden`, `password` | _skipped_ |
154
+ | HTML input | JSON Schema |
155
+ |------------------------------|------------------------------------|
156
+ | `text`, `search`, `tel` | `string` |
157
+ | `email` | `string` + `format: email` |
158
+ | `url` | `string` + `format: uri` |
159
+ | `number`, `range` | `number` (+ `min` / `max`) |
160
+ | `date` | `string` + `format: date` |
161
+ | `datetime-local` | `string` + `format: date-time` |
162
+ | `checkbox` | `boolean` |
163
+ | checkbox group (same `name`) | `array` + `items.enum` |
164
+ | `radio` group | `string` + `enum` + `oneOf` |
165
+ | `select` | `string` + `enum` + `oneOf` |
166
+ | `select[multiple]` | `array` + `items.enum` |
167
+ | `textarea` | `string` |
168
+ | ARIA role inputs | mapped by role (textbox, checkbox…) |
169
+ | `file`, `hidden`, `password` | _skipped_ |
170
+
171
+ Additional schema enrichment:
172
+ - Pre-filled field values are exposed as `schema.default`
173
+ - `<optgroup>` labels and `<datalist>` suggestions are included as metadata
174
+ - Disabled select options and placeholder options (`"Select..."`, `"---"`) are excluded
175
+
176
+ ---
177
+
178
+ ## ToolAnnotations
179
+
180
+ auto-webmcp automatically infers [WebMCP ToolAnnotations](https://webmachinelearning.github.io/webmcp/) from your HTML:
181
+
182
+ | Annotation | Auto-inferred when |
183
+ |---|---|
184
+ | `readOnlyHint` | Form method is `GET`, or submit button says "Search", "Find", etc. |
185
+ | `destructiveHint` | Submit button says "Delete", "Remove", "Cancel", etc. |
186
+ | `idempotentHint` | Form is read-only or GET |
187
+ | `openWorldHint` | Form modifies data (default for POST forms) |
188
+
189
+ Override any annotation with data attributes:
190
+
191
+ ```html
192
+ <form
193
+ data-webmcp-destructive="true"
194
+ data-webmcp-openworld="true"
195
+ >
196
+ <!-- delete account form -->
197
+ </form>
198
+ ```
199
+
200
+ ---
201
+
202
+ ## Advanced discovery
203
+
204
+ ### Shadow DOM (Web Components)
205
+
206
+ Inputs inside custom element shadow roots are automatically discovered and included in the schema. No extra configuration needed.
207
+
208
+ ```html
209
+ <form>
210
+ <custom-date-picker></custom-date-picker> <!-- shadow root inputs discovered -->
211
+ <button type="submit">Book</button>
212
+ </form>
213
+ ```
214
+
215
+ ### Orphan inputs (no `<form>` tag)
216
+
217
+ Input groups not wrapped in a `<form>` element (common in SPAs) are detected by finding the nearest ancestor that contains a visible submit button. Each group is registered as a separate tool.
218
+
219
+ ### React and framework-managed forms
220
+
221
+ auto-webmcp fills React-controlled inputs using native `HTMLInputElement` prototype setters and `execCommand` to trigger `onChange`. A post-fill snapshot preserves field values if the framework re-renders before submit.
222
+
223
+ ---
224
+
225
+ ## Structured execute result
226
+
227
+ Every tool execution returns a two-item `content` array:
228
+
229
+ - `content[0].text` — human-readable summary: `"Form submitted. Fields: {...}"`
230
+ - `content[1].text` — JSON-stringified structured data:
231
+
232
+ ```json
233
+ {
234
+ "status": "success",
235
+ "filled_fields": { "email": "a@b.com", "frequency": "weekly" },
236
+ "skipped_fields": [],
237
+ "missing_required": [],
238
+ "warnings": []
239
+ }
240
+ ```
241
+
242
+ `status` is `"partial"` when required fields were missing or a value was clamped. Each warning in the `warnings` array has a `field`, `type` (`clamped`, `not_filled`, `missing_required`, `type_mismatch`), `message`, and optionally `original` / `actual` values.
135
243
 
136
244
  ---
137
245
 
@@ -148,12 +256,23 @@ handle.destroy() // Promise<void> — unregister all tools & stop observing
148
256
  ### Events
149
257
 
150
258
  ```js
259
+ // auto-webmcp lifecycle events
151
260
  window.addEventListener('form:registered', (e) => {
152
261
  console.log('Registered:', e.detail.toolName, e.detail.form);
153
262
  });
154
263
  window.addEventListener('form:unregistered', (e) => {
155
264
  console.log('Removed:', e.detail.toolName);
156
265
  });
266
+
267
+ // WebMCP spec events
268
+ window.addEventListener('toolactivated', (e) => {
269
+ // Agent has filled the form fields, waiting for submit
270
+ console.log('Agent activated:', e.detail.toolName);
271
+ });
272
+ window.addEventListener('toolcancel', (e) => {
273
+ // User reset the form or agent cancelled
274
+ console.log('Cancelled:', e.detail.toolName);
275
+ });
157
276
  ```
158
277
 
159
278
  ### Prevent auto-init (IIFE build)
@@ -177,16 +296,15 @@ npm install
177
296
  npm run build # compile to dist/
178
297
  npm run build:watch # rebuild on change
179
298
  npm run typecheck # TypeScript type check only
180
- npm test # Playwright integration tests
299
+ npm test # Playwright integration tests (Chromium)
181
300
  ```
182
301
 
183
302
  ---
184
303
 
185
304
  ## Browser support
186
305
 
187
- - Chrome 146+ with `#enable-webmcp-testing` flag (or WebMCP GA) for full functionality
188
- - All other browsers: forms are analyzed and the library loads without error
189
- `navigator.modelContext` calls are silently no-opped (progressive enhancement)
306
+ - Chrome 146+ with `chrome://flags/#enable-webmcp-testing` enabled for full functionality
307
+ - All other browsers: the library loads, analyzes forms, and silently no-ops `navigator.modelContext` calls (progressive enhancement)
190
308
 
191
309
  ---
192
310
 
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAmJ,MAAM,aAAa,CAAC;AAC1L,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAOxF;AAynBD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,KAAK,CAAC,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC,EACzE,SAAS,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GACrD,YAAY,CAKd"}
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../src/analyzer.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAmJ,MAAM,aAAa,CAAC;AAC1L,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,WAAW,eAAe;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,UAAU,CAAC;IACxB,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B,6FAA6F;IAC7F,aAAa,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAKD,iDAAiD;AACjD,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,EAAE,YAAY,GAAG,YAAY,CAOxF;AAipBD;;;GAGG;AACH,wBAAgB,uBAAuB,CACrC,SAAS,EAAE,OAAO,EAClB,MAAM,EAAE,KAAK,CAAC,gBAAgB,GAAG,mBAAmB,GAAG,iBAAiB,CAAC,EACzE,SAAS,EAAE,iBAAiB,GAAG,gBAAgB,GAAG,IAAI,GACrD,YAAY,CAKd"}
@@ -102,7 +102,6 @@ function resolveConfig(userConfig) {
102
102
  return {
103
103
  exclude: userConfig?.exclude ?? [],
104
104
  autoSubmit: userConfig?.autoSubmit ?? false,
105
- enhance: userConfig?.enhance ?? null,
106
105
  overrides: userConfig?.overrides ?? {},
107
106
  debug: userConfig?.debug ?? false
108
107
  };
@@ -503,17 +502,39 @@ function extractDefaultValue(control) {
503
502
  }
504
503
  return void 0;
505
504
  }
505
+ function collectShadowControls(root, visited = /* @__PURE__ */ new Set()) {
506
+ if (visited.has(root))
507
+ return [];
508
+ visited.add(root);
509
+ const results = [];
510
+ for (const el of Array.from(root.querySelectorAll("*"))) {
511
+ if (el.shadowRoot) {
512
+ results.push(
513
+ ...Array.from(
514
+ el.shadowRoot.querySelectorAll(
515
+ "input, textarea, select"
516
+ )
517
+ ),
518
+ ...collectShadowControls(el.shadowRoot, visited)
519
+ );
520
+ }
521
+ }
522
+ return results;
523
+ }
506
524
  function buildSchema(form) {
507
525
  const properties = {};
508
526
  const required = [];
509
527
  const fieldElements = /* @__PURE__ */ new Map();
510
528
  const processedRadioGroups = /* @__PURE__ */ new Set();
511
529
  const processedCheckboxGroups = /* @__PURE__ */ new Set();
512
- const controls = Array.from(
513
- form.querySelectorAll(
514
- "input, textarea, select"
515
- )
516
- );
530
+ const controls = [
531
+ ...Array.from(
532
+ form.querySelectorAll(
533
+ "input, textarea, select"
534
+ )
535
+ ),
536
+ ...collectShadowControls(form)
537
+ ];
517
538
  for (const control of controls) {
518
539
  const name = control.name;
519
540
  const fieldKey = name || resolveNativeControlFallbackKey(control);
@@ -691,9 +712,6 @@ function resolveAriaFieldKey(el) {
691
712
  return null;
692
713
  }
693
714
  function inferAriaFieldTitle(el) {
694
- const nativeTitle = el.getAttribute("toolparamtitle");
695
- if (nativeTitle?.trim())
696
- return nativeTitle.trim();
697
715
  const htmlEl = el;
698
716
  if (htmlEl.dataset?.["webmcpTitle"])
699
717
  return htmlEl.dataset["webmcpTitle"];
@@ -732,9 +750,6 @@ function inferAriaFieldDescription(el) {
732
750
  return "";
733
751
  }
734
752
  function inferFieldTitle(control) {
735
- const nativeTitle = control.getAttribute("toolparamtitle");
736
- if (nativeTitle?.trim())
737
- return nativeTitle.trim();
738
753
  if ("dataset" in control && control.dataset["webmcpTitle"]) {
739
754
  return control.dataset["webmcpTitle"];
740
755
  }
@@ -944,6 +959,9 @@ function buildExecuteHandler(form, config, toolName, metadata) {
944
959
  return async (params) => {
945
960
  pendingFillWarnings.set(form, []);
946
961
  fillFormFields(form, params);
962
+ const missingNow = getMissingRequired(metadata, params);
963
+ if (missingNow.length > 0)
964
+ pendingWarnings.set(form, missingNow);
947
965
  window.dispatchEvent(new CustomEvent("toolactivated", { detail: { toolName } }));
948
966
  return new Promise((resolve, reject) => {
949
967
  pendingExecutions.set(form, { resolve, reject });
@@ -970,9 +988,10 @@ function buildExecuteHandler(form, config, toolName, metadata) {
970
988
  attachSubmitInterceptor(submitForm, toolName);
971
989
  }
972
990
  }
973
- const missing = getMissingRequired(metadata, params);
974
- if (missing.length > 0)
975
- pendingWarnings.set(submitForm, missing);
991
+ if (submitForm !== form && pendingWarnings.has(form)) {
992
+ pendingWarnings.set(submitForm, pendingWarnings.get(form));
993
+ pendingWarnings.delete(form);
994
+ }
976
995
  submitForm.requestSubmit();
977
996
  } catch (err) {
978
997
  reject(err instanceof Error ? err : new Error(String(err)));
@@ -994,17 +1013,37 @@ function attachSubmitInterceptor(form, toolName) {
994
1013
  pendingExecutions.delete(form);
995
1014
  const formData = serializeFormData(form, lastParams.get(form), formFieldElements.get(form));
996
1015
  lastFilledSnapshot.delete(form);
997
- const missing = pendingWarnings.get(form);
1016
+ const missingRequired = pendingWarnings.get(form) ?? [];
998
1017
  pendingWarnings.delete(form);
999
1018
  const fillWarnings = pendingFillWarnings.get(form) ?? [];
1000
1019
  pendingFillWarnings.delete(form);
1001
- const allWarnings = [
1002
- ...missing?.length ? [`required fields were not filled: ${missing.join(", ")}`] : [],
1003
- ...fillWarnings
1020
+ const skippedFields = fillWarnings.filter((w) => w.type === "not_filled").map((w) => w.field);
1021
+ const structured = {
1022
+ status: missingRequired.length > 0 || skippedFields.length > 0 ? "partial" : "success",
1023
+ filled_fields: formData,
1024
+ skipped_fields: skippedFields,
1025
+ missing_required: missingRequired,
1026
+ warnings: [
1027
+ ...missingRequired.map((f) => ({
1028
+ field: f,
1029
+ type: "missing_required",
1030
+ message: `required field "${f}" was not provided`
1031
+ })),
1032
+ ...fillWarnings
1033
+ ]
1034
+ };
1035
+ const allWarnMessages = [
1036
+ ...missingRequired.length ? [`required fields were not filled: ${missingRequired.join(", ")}`] : [],
1037
+ ...fillWarnings.map((w) => w.message)
1004
1038
  ];
1005
- const warningText = allWarnings.length ? ` Note: ${allWarnings.join("; ")}.` : "";
1039
+ const warningText = allWarnMessages.length ? ` Note: ${allWarnMessages.join("; ")}.` : "";
1006
1040
  const text = `Form submitted. Fields: ${JSON.stringify(formData)}${warningText}`;
1007
- const result = { content: [{ type: "text", text }] };
1041
+ const result = {
1042
+ content: [
1043
+ { type: "text", text },
1044
+ { type: "text", text: JSON.stringify(structured) }
1045
+ ]
1046
+ };
1008
1047
  if (e.agentInvoked && typeof e.respondWith === "function") {
1009
1048
  e.preventDefault();
1010
1049
  e.respondWith(Promise.resolve(result));
@@ -1138,16 +1177,26 @@ function fillInput(input, form, key, value) {
1138
1177
  const raw = String(value ?? "");
1139
1178
  const num = Number(raw);
1140
1179
  if (raw === "" || isNaN(num)) {
1141
- pendingFillWarnings.get(form)?.push(`"${key}" expects a number, got: ${JSON.stringify(value)}`);
1180
+ pendingFillWarnings.get(form)?.push({
1181
+ field: key,
1182
+ type: "type_mismatch",
1183
+ message: `"${key}" expects a number, got: ${JSON.stringify(value)}`,
1184
+ original: value
1185
+ });
1142
1186
  return;
1143
1187
  }
1144
1188
  const min = input.min !== "" ? parseFloat(input.min) : -Infinity;
1145
1189
  const max = input.max !== "" ? parseFloat(input.max) : Infinity;
1146
1190
  if (num < min || num > max) {
1147
- pendingFillWarnings.get(form)?.push(
1148
- `"${key}" value ${num} is outside allowed range [${input.min || "?"}, ${input.max || "?"}]`
1149
- );
1150
- input.value = String(Math.min(Math.max(num, min), max));
1191
+ const clamped = Math.min(Math.max(num, min), max);
1192
+ pendingFillWarnings.get(form)?.push({
1193
+ field: key,
1194
+ type: "clamped",
1195
+ message: `"${key}" value ${num} is outside allowed range [${input.min || "?"}, ${input.max || "?"}], clamped to ${clamped}`,
1196
+ original: num,
1197
+ actual: clamped
1198
+ });
1199
+ input.value = String(clamped);
1151
1200
  } else {
1152
1201
  input.value = String(num);
1153
1202
  }
@@ -1334,74 +1383,6 @@ function getMissingRequired(metadata, params) {
1334
1383
  return metadata.inputSchema.required.filter((fieldKey) => !(fieldKey in params));
1335
1384
  }
1336
1385
 
1337
- // src/enhancer.ts
1338
- async function enrichMetadata(metadata, enhancer) {
1339
- try {
1340
- const enriched = await callLLM(metadata, enhancer);
1341
- return { ...metadata, description: enriched };
1342
- } catch (err) {
1343
- console.warn("[auto-webmcp] Enrichment failed, using heuristic description:", err);
1344
- return metadata;
1345
- }
1346
- }
1347
- async function callLLM(metadata, config) {
1348
- const prompt = buildPrompt(metadata);
1349
- if (config.provider === "claude") {
1350
- return callClaude(prompt, config);
1351
- } else {
1352
- return callGemini(prompt, config);
1353
- }
1354
- }
1355
- function buildPrompt(metadata) {
1356
- const fields = Object.entries(metadata.inputSchema.properties).map(([name, prop]) => `- ${prop.title ?? name} (${prop.type}): ${prop.description ?? ""}`).join("\n");
1357
- return `You are helping describe a web form as an AI tool. Given this form information:
1358
-
1359
- Name: ${metadata.name}
1360
- Current description: ${metadata.description}
1361
- Fields:
1362
- ${fields}
1363
-
1364
- Write a concise (1-2 sentence) description of what this tool does and when an AI agent should use it. Be specific and actionable. Respond with only the description, no preamble.`;
1365
- }
1366
- async function callClaude(prompt, config) {
1367
- const model = config.model ?? "claude-haiku-4-5-20251001";
1368
- const response = await fetch("https://api.anthropic.com/v1/messages", {
1369
- method: "POST",
1370
- headers: {
1371
- "x-api-key": config.apiKey,
1372
- "anthropic-version": "2023-06-01",
1373
- "content-type": "application/json"
1374
- },
1375
- body: JSON.stringify({
1376
- model,
1377
- max_tokens: 150,
1378
- messages: [{ role: "user", content: prompt }]
1379
- })
1380
- });
1381
- if (!response.ok) {
1382
- throw new Error(`Claude API error: ${response.status}`);
1383
- }
1384
- const data = await response.json();
1385
- return data.content.filter((block) => block.type === "text").map((block) => block.text).join("").trim();
1386
- }
1387
- async function callGemini(prompt, config) {
1388
- const model = config.model ?? "gemini-1.5-flash";
1389
- const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${config.apiKey}`;
1390
- const response = await fetch(url, {
1391
- method: "POST",
1392
- headers: { "content-type": "application/json" },
1393
- body: JSON.stringify({
1394
- contents: [{ parts: [{ text: prompt }] }],
1395
- generationConfig: { maxOutputTokens: 150, temperature: 0.2 }
1396
- })
1397
- });
1398
- if (!response.ok) {
1399
- throw new Error(`Gemini API error: ${response.status}`);
1400
- }
1401
- const data = await response.json();
1402
- return data.candidates[0]?.content.parts.map((p) => p.text).join("").trim() ?? "";
1403
- }
1404
-
1405
1386
  // src/discovery.ts
1406
1387
  function emit(type, form, toolName) {
1407
1388
  window.dispatchEvent(
@@ -1433,12 +1414,7 @@ async function registerForm(form, config) {
1433
1414
  } catch {
1434
1415
  }
1435
1416
  }
1436
- let metadata = analyzeForm(form, override);
1437
- if (config.enhance) {
1438
- if (config.debug)
1439
- console.debug(`[auto-webmcp] Enriching: ${metadata.name}\u2026`);
1440
- metadata = await enrichMetadata(metadata, config.enhance);
1441
- }
1417
+ const metadata = analyzeForm(form, override);
1442
1418
  if (config.debug) {
1443
1419
  warnToolQuality(metadata.name, metadata.description);
1444
1420
  }