auto-webmcp 0.3.1 → 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 +156 -38
- package/dist/auto-webmcp.cjs.js +1 -75
- package/dist/auto-webmcp.cjs.js.map +3 -3
- package/dist/auto-webmcp.esm.js +1 -75
- package/dist/auto-webmcp.esm.js.map +3 -3
- package/dist/auto-webmcp.iife.js +1 -9
- package/dist/auto-webmcp.iife.js.map +4 -4
- package/dist/config.d.ts +0 -10
- package/dist/config.d.ts.map +1 -1
- package/dist/discovery.d.ts.map +1 -1
- package/package.json +1 -1
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.
|
|
41
|
-
3.
|
|
42
|
-
4.
|
|
43
|
-
5.
|
|
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
|
-
|
|
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. `
|
|
106
|
-
2.
|
|
107
|
-
3.
|
|
108
|
-
4.
|
|
109
|
-
5.
|
|
110
|
-
6.
|
|
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. `
|
|
115
|
-
2.
|
|
116
|
-
3.
|
|
117
|
-
4. `aria-
|
|
118
|
-
5.
|
|
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
|
|
123
|
-
|
|
124
|
-
| `text`, `search`, `tel
|
|
125
|
-
| `email`
|
|
126
|
-
| `url`
|
|
127
|
-
| `number`, `range`
|
|
128
|
-
| `date`
|
|
129
|
-
| `datetime-local`
|
|
130
|
-
| `checkbox`
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `
|
|
134
|
-
| `
|
|
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
|
|
188
|
-
- All other browsers:
|
|
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
|
|
package/dist/auto-webmcp.cjs.js
CHANGED
|
@@ -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
|
};
|
|
@@ -1384,74 +1383,6 @@ function getMissingRequired(metadata, params) {
|
|
|
1384
1383
|
return metadata.inputSchema.required.filter((fieldKey) => !(fieldKey in params));
|
|
1385
1384
|
}
|
|
1386
1385
|
|
|
1387
|
-
// src/enhancer.ts
|
|
1388
|
-
async function enrichMetadata(metadata, enhancer) {
|
|
1389
|
-
try {
|
|
1390
|
-
const enriched = await callLLM(metadata, enhancer);
|
|
1391
|
-
return { ...metadata, description: enriched };
|
|
1392
|
-
} catch (err) {
|
|
1393
|
-
console.warn("[auto-webmcp] Enrichment failed, using heuristic description:", err);
|
|
1394
|
-
return metadata;
|
|
1395
|
-
}
|
|
1396
|
-
}
|
|
1397
|
-
async function callLLM(metadata, config) {
|
|
1398
|
-
const prompt = buildPrompt(metadata);
|
|
1399
|
-
if (config.provider === "claude") {
|
|
1400
|
-
return callClaude(prompt, config);
|
|
1401
|
-
} else {
|
|
1402
|
-
return callGemini(prompt, config);
|
|
1403
|
-
}
|
|
1404
|
-
}
|
|
1405
|
-
function buildPrompt(metadata) {
|
|
1406
|
-
const fields = Object.entries(metadata.inputSchema.properties).map(([name, prop]) => `- ${prop.title ?? name} (${prop.type}): ${prop.description ?? ""}`).join("\n");
|
|
1407
|
-
return `You are helping describe a web form as an AI tool. Given this form information:
|
|
1408
|
-
|
|
1409
|
-
Name: ${metadata.name}
|
|
1410
|
-
Current description: ${metadata.description}
|
|
1411
|
-
Fields:
|
|
1412
|
-
${fields}
|
|
1413
|
-
|
|
1414
|
-
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.`;
|
|
1415
|
-
}
|
|
1416
|
-
async function callClaude(prompt, config) {
|
|
1417
|
-
const model = config.model ?? "claude-haiku-4-5-20251001";
|
|
1418
|
-
const response = await fetch("https://api.anthropic.com/v1/messages", {
|
|
1419
|
-
method: "POST",
|
|
1420
|
-
headers: {
|
|
1421
|
-
"x-api-key": config.apiKey,
|
|
1422
|
-
"anthropic-version": "2023-06-01",
|
|
1423
|
-
"content-type": "application/json"
|
|
1424
|
-
},
|
|
1425
|
-
body: JSON.stringify({
|
|
1426
|
-
model,
|
|
1427
|
-
max_tokens: 150,
|
|
1428
|
-
messages: [{ role: "user", content: prompt }]
|
|
1429
|
-
})
|
|
1430
|
-
});
|
|
1431
|
-
if (!response.ok) {
|
|
1432
|
-
throw new Error(`Claude API error: ${response.status}`);
|
|
1433
|
-
}
|
|
1434
|
-
const data = await response.json();
|
|
1435
|
-
return data.content.filter((block) => block.type === "text").map((block) => block.text).join("").trim();
|
|
1436
|
-
}
|
|
1437
|
-
async function callGemini(prompt, config) {
|
|
1438
|
-
const model = config.model ?? "gemini-1.5-flash";
|
|
1439
|
-
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${config.apiKey}`;
|
|
1440
|
-
const response = await fetch(url, {
|
|
1441
|
-
method: "POST",
|
|
1442
|
-
headers: { "content-type": "application/json" },
|
|
1443
|
-
body: JSON.stringify({
|
|
1444
|
-
contents: [{ parts: [{ text: prompt }] }],
|
|
1445
|
-
generationConfig: { maxOutputTokens: 150, temperature: 0.2 }
|
|
1446
|
-
})
|
|
1447
|
-
});
|
|
1448
|
-
if (!response.ok) {
|
|
1449
|
-
throw new Error(`Gemini API error: ${response.status}`);
|
|
1450
|
-
}
|
|
1451
|
-
const data = await response.json();
|
|
1452
|
-
return data.candidates[0]?.content.parts.map((p) => p.text).join("").trim() ?? "";
|
|
1453
|
-
}
|
|
1454
|
-
|
|
1455
1386
|
// src/discovery.ts
|
|
1456
1387
|
function emit(type, form, toolName) {
|
|
1457
1388
|
window.dispatchEvent(
|
|
@@ -1483,12 +1414,7 @@ async function registerForm(form, config) {
|
|
|
1483
1414
|
} catch {
|
|
1484
1415
|
}
|
|
1485
1416
|
}
|
|
1486
|
-
|
|
1487
|
-
if (config.enhance) {
|
|
1488
|
-
if (config.debug)
|
|
1489
|
-
console.debug(`[auto-webmcp] Enriching: ${metadata.name}\u2026`);
|
|
1490
|
-
metadata = await enrichMetadata(metadata, config.enhance);
|
|
1491
|
-
}
|
|
1417
|
+
const metadata = analyzeForm(form, override);
|
|
1492
1418
|
if (config.debug) {
|
|
1493
1419
|
warnToolQuality(metadata.name, metadata.description);
|
|
1494
1420
|
}
|