diff --git a/docs/README.skills.md b/docs/README.skills.md index 3f7f8cad6..17ee92e10 100644 --- a/docs/README.skills.md +++ b/docs/README.skills.md @@ -406,6 +406,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to | [vscode-ext-localization](../skills/vscode-ext-localization/SKILL.md)
`gh skills install github/awesome-copilot vscode-ext-localization` | Guidelines for proper localization of VS Code extensions, following VS Code extension development guidelines, libraries and good practices | None | | [web-design-reviewer](../skills/web-design-reviewer/SKILL.md)
`gh skills install github/awesome-copilot web-design-reviewer` | This skill enables visual inspection of websites running locally or remotely to identify and fix design issues. Triggers on requests like "review website design", "check the UI", "fix the layout", "find design problems". Detects issues with responsive design, accessibility, visual consistency, and layout breakage, then performs fixes at the source code level. | `references/framework-fixes.md`
`references/visual-checklist.md` | | [webapp-testing](../skills/webapp-testing/SKILL.md)
`gh skills install github/awesome-copilot webapp-testing` | Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs. | `assets/test-helper.js` | +| [webmcpify](../skills/webmcpify/SKILL.md)
`gh skills install github/awesome-copilot webmcpify` | Make a web app agent-ready — propose a WebMCP tool manifest, integrate, verify in a real browser, heal; unrelated code stays untouched. Use for "webmcpify", "add WebMCP", or "expose app actions to AI agents". | `references/heal.md`
`references/integrate.md`
`references/inventory.md`
`references/runtime.md`
`references/security.md`
`references/verify.md`
`templates` | | [what-context-needed](../skills/what-context-needed/SKILL.md)
`gh skills install github/awesome-copilot what-context-needed` | Ask Copilot what files it needs to see before answering a question | None | | [winmd-api-search](../skills/winmd-api-search/SKILL.md)
`gh skills install github/awesome-copilot winmd-api-search` | Find and explore Windows desktop APIs. Use when building features that need platform capabilities — camera, file access, notifications, UI controls, AI/ML, sensors, networking, etc. Discovers the right API for a task and retrieves full type details (methods, properties, events, enumeration values). | `LICENSE.txt`
`scripts/Invoke-WinMdQuery.ps1`
`scripts/Update-WinMdCache.ps1`
`scripts/cache-generator` | | [winui3-migration-guide](../skills/winui3-migration-guide/SKILL.md)
`gh skills install github/awesome-copilot winui3-migration-guide` | UWP-to-WinUI 3 migration reference. Maps legacy UWP APIs to correct Windows App SDK equivalents with before/after code snippets. Covers namespace changes, threading (CoreDispatcher to DispatcherQueue), windowing (CoreWindow to AppWindow), dialogs, pickers, sharing, printing, background tasks, and the most common Copilot code generation mistakes. | None | diff --git a/skills/webmcpify/SKILL.md b/skills/webmcpify/SKILL.md new file mode 100644 index 000000000..64ad9e64b --- /dev/null +++ b/skills/webmcpify/SKILL.md @@ -0,0 +1,331 @@ +--- +name: webmcpify +description: 'Make a web app agent-ready — propose a WebMCP tool manifest, integrate, verify in a real browser, heal; unrelated code stays untouched. Use for "webmcpify", "add WebMCP", or "expose app actions to AI agents".' +argument-hint: "[inventory|integrate|verify|status|full] [scope notes]" +license: MIT +metadata: + source: https://github.com/TueJon/webmcpify +--- + +# webmcpify — make any web app agent-ready, verifiably + +You are running the webmcpify pipeline. It takes an existing web application and +exposes its user-facing functionality as [WebMCP](https://webmachinelearning.github.io/webmcp/) +tools (`document.modelContext` — a proposed web standard incubated in the W3C Web +Machine Learning Community Group, currently a Chrome origin trial), so browser AI +agents can operate the app through structured tool calls instead of guessing at the DOM. + +``` +DETECT ──▶ INVENTORY ──▶ [HUMAN GATE: manifest approval] ──▶ INTEGRATE ──▶ VERIFY ──▶ HEAL ──▶ AUDIT + ▲ loop per-area batches on big apps ▲ loop ▲ loop ▲ loop + └── per area └── per manifest entry ──┘ +``` + +Everything you need ships inside this skill directory: phase guides in +`references/`, and vendorable code in `templates/` (runtime, ambient types, +JS variant, React JSX typings, verification spec). Never assume files exist +outside the skill dir. + +**Out of scope** (stop and say so): backend-only MCP servers (that's classic MCP, +not WebMCP), automating third-party sites you don't control, and generic SEO work. + +## Invocation modes + +The user may pass an argument (`/webmcpify ` or plain words): + +| Argument | Run | Stop at | +|---|---|---| +| *(none)* or `full` | all phases, resuming from current manifest state | done | +| `inventory` / `map` | DETECT + INVENTORY loops only — **zero code changes** | present the manifest table for review | +| `integrate` | INTEGRATE loop only (requires approved tools in the manifest) | integrated + built | +| `verify` | VERIFY + HEAL loops on integrated/verified tools | green/skipped report | +| `status` | read `.webmcpify/manifest.json` — **read-only** | report phase, per-status tool counts, and the recommended next command | + +Any other text is scoping guidance (e.g. "only the checkout area", "read-only tools only"). + +## Ground rules (non-negotiable, enforce in every phase) + +1. **Zero unrelated changes.** Every diff hunk you produce must trace to a manifest + entry or the recorded one-time setup. Never refactor, reformat, rename, or + "improve" anything else — note problems in the report instead. Files that were + already dirty at baseline (recorded in the manifest) are **untouchable**: never + modify or revert them. +2. **Read-only tools first.** Mutations are tri-state: `mutating: false`, + `"client"` (browser-local only: prefs, localStorage), or `"server"` (data + leaves the browser). Server-mutating tools require explicit **per-tool** human + approval recorded in the manifest; client-mutating tools may be approved as a + batch at the gate. Never expose destructive, irreversible, or payment actions + in a first integration. +3. **The server is the only trust boundary.** A tool's `execute()` may only call code + paths the UI already uses (same endpoints, same validation, same auth). Never + create new endpoints, never bypass existing checks, never put secrets in tools. +4. **Spec-shaped and dependency-free.** Register via `document.modelContext.registerTool()` + with AbortSignal lifecycle (feature-detect the deprecated `navigator.modelContext` + fallback). No third-party WebMCP runtime dependencies. Everything feature-detected: + the app behaves identically in browsers without WebMCP. +5. **Never `toolautosubmit` on state-changing forms** — neither `mutating: "client"` + nor `"server"`. Only on pure read forms (search, filter, availability). +6. **State lives in files, not in your context.** Read/write `.webmcpify/` constantly; + assume your context can be wiped between any two steps. Write the manifest + atomically (write `manifest.json.tmp`, then rename over `manifest.json`). +7. **Commits are opt-in.** Never commit unless the human chose a commit policy at + the gate (see below). Without git or without permission, leave changes in the + working tree and record progress in the manifest only. + +## Fresh, authoritative guidance + +WebMCP is an evolving origin-trial API — the surface has already changed during the +trial (testing API removed 2026-07; `navigator` → `document`). Before Phase 2, if +network is available, pull Google's current official guides rather than relying on +memory: + +```sh +npx -y modern-web-guidance@latest retrieve "webmcp,agentic-forms,agentic-javascript-tools" +``` + +If offline, use `references/integrate.md` — but prefer the live guides when they conflict. + +## The state protocol — `.webmcpify/` in the target repo + +| File | Purpose | +|---|---| +| `manifest.json` | Single source of truth (schema below; atomic writes) | +| `areas/.tools.json` | Sub-agent shard output during inventory fan-out (merged, then deleted) | +| `report.md` | Human-facing running report; finalized at the end | + +**Resume rule:** if `manifest.json` exists, resume — recompute nothing already +recorded. **Merge leftover shards FIRST**: any existing `areas/.tools.json` +files are merged into the manifest (mark those areas `inventoried`, delete the +shards) before redispatching any sub-agents. Then continue at `pipeline.phase`, +the first `pending` area, or the first tool whose status is not terminal. +Terminal statuses: `verified`, `skipped`, `rejected`. + +**Phase transitions** (make the atomic manifest write the moment the condition holds): + +- `detect → inventory`: `app` recorded, `baselineSha`/`baselineDirty` captured. +- `inventory → gate`: no area `pending`, completeness pass has run. +- `gate → integrate`: every `discovered` tool is `approved`/`rejected`, and + `commitPolicy` + `commitWebmcpifyDir` are set. +- `integrate → verify`: no `approved` tools remain (each `integrated` or terminal), + build green. +- `verify → heal`: verify loop visited every `integrated` tool and ≥1 is `failed` + (none failed → straight to `audit`). +- `heal → audit`: no tool `failed` and post-heal full re-verify passed. +- `audit → done`: every hunk mapped-or-flagged, `report.md` finalized. + +Manifest schema (Webmcpify Manifest v3): + +```jsonc +{ + "webmcpify": 3, + "app": { "stack": "react-vite", "typescript": true, "entry": "src/main.tsx", + "baseUrl": "http://localhost:5173", "startCommand": "npm run dev", + "authFixtures": { // how verify OBTAINS each session + "member": { "obtain": "npm run seed:test-user, then sign in at /login", + "account": "member@example.test", + "env": ["TEST_MEMBER_PASSWORD"] } // env var NAMES only — never secret values + } }, + "pipeline": { + "phase": "inventory", // detect|inventory|gate|integrate|verify|heal|audit|done — transition rules above + "setup": { // PATHS created/modified per one-time setup step ([] = not done yet) + "runtimeVendored": ["src/webmcp/webmcpify.ts", "src/webmcp/webmcp.d.ts"], + "harnessInstalled": [".webmcpify/webmcp.spec.ts"], + "originTrialNoted": ["README.md"] + }, + "baselineSha": "abc1234", // HEAD at pipeline start; null if no git + "baselineDirty": ["src/wip.ts"], // paths dirty at start — untouchable (ground rule 1) + "commitPolicy": null, // set at the gate: "commit-per-batch" | "no-commit" + "commitWebmcpifyDir": null, // set at the gate: commit .webmcpify/ itself? true | false + "blockers": [] // e.g. "app won't start locally: needs $API_KEY" — surfaced at the gate + }, + "areas": [ + { "id": "checkout", "paths": ["src/features/checkout/"], "status": "pending" } // pending|inventoried + ], + "tools": [ + { + "id": "create_ticket", + "area": "tickets", + "kind": "imperative", // imperative | declarative + "mutating": "server", // false | "client" (browser-local only: prefs, localStorage) | "server" (data leaves the browser) + "priority": 1, // 1 = expose first; 2/3 = later waves + "description": "Creates a new ticket in the currently open project.", + "inputSchema": { /* JSON Schema */ }, + "annotations": { "readOnlyHint": false, "untrustedContentHint": false }, // verify asserts these on the enumerated tool + "source": ["src/features/tickets/NewTicket.tsx:42"], // the UI code path it wraps + "route": "/projects/demo/tickets", // where verify navigates + "auth": ["role:member"], // "none" | "session" | ["role:", ...] — keys into app.authFixtures; verify runs once per listed role + "examples": { "valid": { "title": "Test ticket" }, "invalid": {} }, + // invalid: null ONLY for readOnlyHint tools with no/empty params — + // verify then asserts dual-outcome: rejects OR resolves with no side effect + "expect": { "result": "created", "navigation": null, "ui": "new row appears in the ticket list" }, + // exactly one of result|navigation: result = substring of the resolved string; + // navigation = destination URL/pattern when executeTool resolves null (it navigated) + "cleanup": "delete the created ticket via the UI's own delete path (test data only)", // required for mutating:"server", recommended for "client" + "status": "discovered", // discovered|approved|rejected*|integrated|verified*|failed|skipped* (* = terminal) + "approval": null, // server-mutating tools, once approved: { "note": "...", "at": "2026-07-12", + // "productionSideEffect": null } — set only when verification unavoidably + // causes a real production effect (see VERIFY: production side-effect policy) + "attempts": 0, // heal-fix cycles; the triggering verify failure is attempt 0 + "batchCommit": null, // sha under commit-per-batch — lands in the manifest one commit LATER + "notes": "" + } + ], + "log": [ "2026-07-12 inventory: area checkout done, 4 candidates" ] +} +``` + +**v2→v3 migration:** resuming a `"webmcpify": 2` manifest migrates in place on +first write — `auth` string → array; `setup` booleans → path arrays (`false` → +`[]`; `true` → recover paths from git/`log`, else `null` = done-but-unrecorded, +audit treats those files flag-only); `mutating: true` → `"server"`; add +`annotations` (defaults from the inventory table), `blockers: []`, +`commitWebmcpifyDir: null`, `expect.navigation: null`; then bump to 3. + +## Phase 0 — DETECT + +Identify stack, build + dev-server commands, TypeScript or not, auth model +(including how verify obtains each test session → `app.authFixtures`), test +setup, and how the app starts locally; record under `app`. Record the git baseline: +`pipeline.baselineSha` = current HEAD and `pipeline.baselineDirty` = `git status +--porcelain` paths (both `null`/`[]` without git). If the app cannot be started +locally, append the blocker to `pipeline.blockers` — integration may proceed, but +verification will be blocked and this must be surfaced at the gate. Details: +`references/inventory.md`. + +## Phase 1 — INVENTORY (loop; scales to any size) + +**Never map a large codebase in one pass.** + +1. **Area map first (cheap, structural):** enumerate routes/views/feature modules + from the router config, pages directory, or navigation — without reading + implementation files. Write every area to `areas` with `"pending"`. +2. **Inventory loop — one area per iteration:** deep-read only that area's files; + draft a candidate tool per user action (conventions, tool-count budget, and + overlap rules: `references/inventory.md`) with ALL manifest fields filled, + including `route`, `auth`, `annotations`, `examples`, `expect`, and `cleanup` + (required for `mutating: "server"`, recommended for `"client"`) — the verify + phase runs from these fields alone. Append as `"discovered"`, mark the area + `"inventoried"`, write the manifest, repeat. + - **Sub-agent fan-out:** sub-agents never write `manifest.json`. Each writes only + its own `areas/.tools.json` shard — schema + `{ "webmcpifyShard": 3, "area": "", "tools": [ /* full v3 tool entries */ ] }`, + written atomically (tmp + rename). You (the coordinator) merge shards into + the manifest sequentially, then delete them; on resume, merge existing + shards FIRST before redispatching (Resume rule). +3. **Exit:** no `pending` areas remain, plus one completeness pass — walk the app's + navigation and ask "is any visible user action missing?" + +## GATE — manifest approval (the one main checkpoint) + +Present the manifest compactly (id, area, kind, mutating, priority, one-line +description) — per-area batches on large apps. Ask the human to decide, in one +exchange where possible: + +1. Which tools are `approved` vs `rejected` (**`rejected` is terminal** — rejected + tools are excluded from every later phase and from exit conditions). + `mutating: "server"` tools need individual acknowledgment → record in + `approval`; `mutating: "client"` tools may be approved as a batch. +2. **Commit policy**: `commit-per-batch` (each integration batch committed, + revertable — recommended on a clean baseline) or `no-commit` (leave changes + uncommitted for the human to review/commit) → `pipeline.commitPolicy`. Also + whether `.webmcpify/` itself should be committed (recommended: yes — it + documents the integration) → `pipeline.commitWebmcpifyDir`. +3. Every entry in `pipeline.blockers` (e.g. app won't start). If verifying a tool + will unavoidably cause a real production side effect (e.g. a mailer with an + Origin-allow-listed endpoint), get that approved HERE and record it in the + tool's `approval.productionSideEffect` — see VERIFY. + +Apply `references/security.md` to every mutating tool **before** presenting. + +## Phase 2 — INTEGRATE (loop) + +One-time setup first — record the created/modified file **paths** in +`pipeline.setup` (e.g. `runtimeVendored: ["src/webmcp/webmcpify.ts", ...]`): +vendor the runtime from this skill's `templates/` (`webmcpify.ts`, or +`webmcpify.js` for non-TS projects, plus `webmcp.d.ts` for TS and +`webmcp-jsx.d.ts` for React TSX — keep the full MIT header; see +`references/runtime.md`) and note the origin-trial/flag requirement in the target +README (`originTrialNoted`). Then loop: + +1. Pick the next batch of `approved` tools — one area or ≤5 tools. +2. Implement per `references/integrate.md`: declarative attributes for standard + HTML forms (including framework-rendered and fetch-intercepted ones); + imperative registration via the vendored runtime for non-form or + controlled-state actions. +3. Build + typecheck; fix only what the batch broke. +4. Mark tools `"integrated"`, write the manifest. Under `commit-per-batch`: + require a **clean index** before staging (unrelated staged changes → stop and + surface); stage **only the batch's files by path** — never `git add -A`, `-u`, + `.`, or `commit -a`; commit `feat(webmcp): expose (webmcpify)`. The + commit sha lands in `batchCommit` on the **next** manifest write — one commit + later (the manifest can't contain its own commit's sha). Never amend a + previous batch commit. +5. Repeat until no `approved` tools remain. + +## Phase 3 — VERIFY (loop) + +Set up once from `templates/webmcp.spec.ts` per `references/verify.md` (real headed +Chrome; production `getTools()`/`executeTool()` surface with legacy fallback probe). +Then loop over every `integrated` tool, using its manifest `route`, `auth`, +`examples`, `expect`, and `annotations` fields: + +- assert the tool is registered with the expected schema (enumerated `inputSchema` + is a *stringified* JSON Schema — parse before comparing) **and** the manifest + `annotations`; +- execute the valid example (mutating tools: dev/test data only, then run + `cleanup`) and one invalid example (`invalid: null` zero-param read tools: + dual-outcome assertion — see `references/verify.md`); +- assert on the returned result **and** the resulting UI state per `expect` + (a UI **delta**, or `expect.navigation` when execution resolves `null`). + +Pass → `"verified"`. Fail → `"failed"` + failure note. Role-scoped tools: run the +loop once per role listed in `auth`, signing in via the matching +`app.authFixtures` entry. + +**Production side-effect policy** — when a tool's verification unavoidably causes +a real production effect (e.g. an email actually sent), ALL THREE are required: +(1) the human approved it at the gate, recorded in `approval.productionSideEffect`; +(2) every test payload is marked `[webmcpify verification]`; (3) the effect is +listed in `report.md`. Without the recorded approval, don't execute the live +path — mark the tool `skipped` with a blocker note. + +## Phase 4 — HEAL (loop) + +While any tool is `"failed"`: diagnose via `references/heal.md`, fix **only** that +tool's integration — **implementation-only** fixes; if the fix would change the +approved contract (schema, description, `mutating` class, `annotations`, +`expect`), go back to the gate for re-approval instead of silently changing the +manifest. The triggering verify failure is attempt 0; increment `attempts` per +fix cycle and re-verify. At `attempts` = 3 → `"skipped"` with a clear blocker +note (an explicit escalation to the human, not a silent drop). Never widen the +diff or fake a pass. After healing, re-run verification once for **all** tools +with status `integrated` or `verified` (healing one tool can break another — +scope collisions). + +**Exit:** every tool is `verified`, `skipped`, or `rejected`; build green. + +## Final — AUDIT + report + +1. **Diff audit (flag-only, never auto-revert):** collect the pipeline's changes — + `git diff ..HEAD` **plus the index and untracked files** under + `commit-per-batch`, or the working tree + index + untracked under `no-commit`. + Every hunk must map to a manifest entry or a recorded `pipeline.setup` path. + An unmapped hunk → **flag it in the report** with file/line and a suggested + disposition; never revert anything yourself. A hunk in a `baselineDirty` file + → untouchable, flag only. Without a `baselineSha`, audit the files named in + manifest `source` fields and `pipeline.setup` paths (setup entries recorded as + `null` by the v2→v3 migration: fall back to flag-only for those files). +2. Finalize `.webmcpify/report.md`: tool coverage per area, skipped/rejected tools + with reasons, security notes (which mutating tools exist, what guards them, + any recorded production side effects), how to test manually (flag, DevTools + WebMCP pane, inspector extension), and every blocker that needs a human. +3. Tell the human: what's exposed, what's skipped and why, and how to try it. + +## References (read on demand, not upfront) + +- `references/inventory.md` — area mapping, naming/schema conventions, budgets/overlap +- `references/integrate.md` — declarative + imperative patterns per stack +- `references/runtime.md` — vendoring + wiring the `templates/` runtime +- `references/verify.md` — harness setup: flags, surfaces, Playwright/Puppeteer, evals +- `references/heal.md` — failure taxonomy → fixes +- `references/security.md` — the security checklist (apply before the gate and at audit) diff --git a/skills/webmcpify/references/heal.md b/skills/webmcpify/references/heal.md new file mode 100644 index 000000000..2173cca81 --- /dev/null +++ b/skills/webmcpify/references/heal.md @@ -0,0 +1,69 @@ +# Heal — failure taxonomy → fixes + +Work one failed tool at a time. Re-verify after each fix. The triggering verify +failure counts as attempt 0; each fix cycle increments `attempts`. At `attempts` += 3 → mark `skipped` with a blocker note (this is an explicit escalation to the +human in the final report, not a silent drop) and move on. **Never** widen the +diff, disable a check, or fake a return value to force a pass. **Mutating +tools:** run the manifest `cleanup` between attempts — retrying a mutation +without cleanup duplicates data. + +**Heal fixes implementations, not contracts.** The manifest is the +human-approved contract: if the correct fix would change a tool's `inputSchema`, +`description`, `mutating` class, `annotations`, or `expect`, take it back to the +gate as a mini re-approval — never silently edit the manifest to match the code. + +## Taxonomy + +| Symptom | Likely cause | Fix | +|---|---|---| +| Tool absent from enumeration | **Registration is async** — the test asserted before `registerTool()` settled; or registration never ran (bootstrap not reached, view not mounted) or wrong Chrome build/flags | FIRST make the test poll (`waitForTool`) or await `toolchange` — only if it still fails, trace the registration call; confirm `isWebMCPAvailable()` in the test env; current Chrome + `--enable-features=WebMCP,WebMCPTesting` | +| Whole scope absent | A registration in the batch rejected (duplicate name, invalid schema, policy) — the runtime rolls back the entire scope | Check console for the `onError` report; fix the offending tool contract | +| Tool absent after route change | Scope disposed by navigation (over-scoping) | Move to static app-level registration unless genuinely view-bound | +| Declarative tool missing | `toolname` typo, frame without `allow="tools"`, or page sends `Origin-Agent-Cluster: ?0` | Fix attribute; check Permissions-Policy `tools` and origin-keying headers | +| Schema mismatch (declarative) | Control lacks `name`, description not resolvable, unsupported control type in this build | Add `name`/`toolparamdescription`/`label[for]`; unsupported controls → switch that form to imperative | +| Schema mismatch (imperative) | Manifest and code drifted | Make code match the approved manifest; if the manifest was wrong, that's a contract change — take it back to the gate for re-approval (see above), never silently update it | +| Assertion compares object to string | Enumerated `inputSchema` is a stringified JSON Schema | `JSON.parse` before comparing (see `verify.md`) | +| `executeTool` returns `null` unexpectedly | The execution navigated (normal for submit-navigating declarative forms) | Assert on the post-navigation page instead of the return value | +| `executeTool` rejects | Schema violation or declarative-validation failure — rejection IS the failure signal for these | For invalid-input tests on declarative tools, assert rejection, not an `"ERROR:"` string | +| Mutating declarative execution hangs until timeout | Chrome fills the form, then **pauses the execution awaiting a real submit interaction** — awaiting `executeTool` alone deadlocks | Use the concurrent pattern in the spec template: start `executeTool` unawaited → wait for the agent-filled value → click submit → await. **NEVER heal by adding `toolautosubmit`** (ground rule 5) | +| Backend rejects the harness with 403/CORS despite correct auth | The endpoint **allow-lists the production `Origin`** (mailers, form gateways) — the localhost harness origin is refused before the tool logic runs, and no local fix exists | Verify the live path with the env-gated server-side replay (§Origin-allow-listed endpoints below), only with the production side-effect approval recorded in `approval.productionSideEffect` (see §Origin-allow-listed endpoints below); without it, mark the live path `skipped` with a blocker note | +| Execution times out / canned success while UI still loading | Completion event fired before the async work finished, or listener missing/wrong event name | Fire `tool-completion-` with `{ ok, message/error }` AFTER awaiting the real work (`runtime.md` contract) | +| Returns success but UI unchanged | `execute()` bypassed the real UI path (parallel implementation) | Rewrite to call the same handler/store action/endpoint the UI uses | +| Invalid input resolves successfully (imperative) | Missing in-code validation | Validate strictly in code; return `"ERROR: "` | +| Fetch-submitted form: agent gets nothing | `preventDefault()` without `respondWith()` | Add the `e.agentInvoked → e.respondWith(promise)` bridge | +| Works manually, fails in Playwright | Headless, missing flags, or profile without the flag | Headed + flags; persistent context; `xvfb-run` in CI | +| 401/403 from `execute()` in test | Tool registered outside the authenticated scope, or test session lacks the role in the manifest `auth` field | Role-scope the registration; sign in with the recorded fixture | +| Flaky: passes alone, fails in suite | Shared state between tool executions | Isolate test data per tool run (use `cleanup`); don't reorder tests to hide it | + +## Origin-allow-listed endpoints — the replay pattern + +Some production backends (mailers, form gateways) allow-list the production +`Origin` header and refuse everything else — the localhost harness can never +exercise the live path directly. When (and only when) the gate approved the real +production side effect (`approval.productionSideEffect`), verify the live path +with an env-gated replay: intercept the app's own request in Playwright and +re-issue it server-side (Node context — not subject to browser CORS) with the +production `Origin`: + +```ts +// Env-gated: runs only with WEBMCP_LIVE_MUTATIONS=1 — never default-on in CI. +if (process.env.WEBMCP_LIVE_MUTATIONS === '1') { + await page.route('**/api/contact', async (route) => { + const response = await context.request.fetch(route.request(), { + headers: { ...route.request().headers(), origin: 'https://example.com' }, // the prod Origin + }); + await route.fulfill({ response }); + }); +} +``` + +This causes a REAL production side effect. Mark every payload +`[webmcpify verification]`, run the manifest `cleanup`, list the effect in +`report.md`, and never enable the gate by default in CI. + +## After healing + +Re-run verification once for **all** tools with status `integrated` or `verified` +(not only the healed ones) — healing one tool can unregister or break another; +scope collisions are the classic case. Only then evaluate the exit condition. diff --git a/skills/webmcpify/references/integrate.md b/skills/webmcpify/references/integrate.md new file mode 100644 index 000000000..6874f30a9 --- /dev/null +++ b/skills/webmcpify/references/integrate.md @@ -0,0 +1,141 @@ +# Integrate — patterns per stack + +> Prefer the live official guides when online: +> `npx -y modern-web-guidance@latest retrieve "webmcp,agentic-forms,agentic-javascript-tools"`. +> The patterns below follow Google's reference implementations +> (GoogleChromeLabs/webmcp-tools) and the W3C CG draft. + +## Declarative — standard HTML forms + +Applies to plain HTML, SSG-emitted, server-rendered, and framework-rendered +(uncontrolled) forms — anywhere a real `
` with named controls exists. +Annotate the existing form; do not restructure it. + +```html + + + + + +
+``` + +Rules: +- The browser derives the JSON Schema from the controls — every control needs + `name`, a resolvable description (`toolparamdescription` → `label[for]` text → + `aria-description`), and correct HTML constraints. Radio groups: description on + the enclosing `
`. +- `toolautosubmit` **only** on pure read forms (search/filter/availability). + Never on contact/checkout/settings/messaging forms. +- Fetch-submitted forms (`preventDefault()`) MUST route the result back to the + agent — the most common integration bug is a swallowed submit: + +```js +form.addEventListener('submit', (e) => { + e.preventDefault(); + const result = doSubmit(new FormData(e.target)) + .then(() => 'Request received. Reply within one business day.'); + if (e.agentInvoked) e.respondWith(result); // pass the PROMISE, not a value +}); +``` + +- Optional UX (verbatim from Chrome docs): style agent activity with + `form:tool-form-active` / `:tool-submit-active` CSS pseudo-classes. +- Forms that navigate to a thank-you page: `executeTool` returns `null` on + navigation (expected). A JSON-LD `{"@type":"Message","text":"…"}` block on the + target page is best-effort garnish — the mechanism is still under spec debate; + never make behavior depend on it. + +### Framework notes — React + +- Vendor `templates/webmcp-jsx.d.ts` alongside the ambient types so strict TSX + accepts `toolname`/`tooldescription`/`toolparamdescription` (it augments the + React attribute interfaces; it is a MODULE file — keep it separate from + `webmcp.d.ts`). +- The typings are string-valued (boolean-attribute style): write + `toolautosubmit=""` — and only on pure read forms (ground rule 5). +- In `onSubmit`, the WebMCP fields live on the NATIVE event: + + ```tsx + const native = e.nativeEvent as SubmitEvent; + if (native.agentInvoked) native.respondWith?.(doSubmit(new FormData(e.currentTarget)) + .then(() => 'Request received. Reply within one business day.')); + ``` + + Pass the PROMISE of the result string, not an already-resolved value. + +## Imperative — SPAs and dynamic apps + +Use the vendored runtime (`runtime.md`). Tools live in a dedicated module per app +(e.g. `src/webmcp/tools.ts`), decoupled from components: + +```ts +import { createToolScope, dispatchAndWait } from './webmcpify'; + +export const searchTicketsTool = { + name: 'search_tickets', + description: 'Searches tickets in the currently open project and shows results on screen.', + inputSchema: { + type: 'object', + properties: { + query: { type: 'string', description: 'Search terms, exactly as the user phrased them.' }, + }, + required: ['query'], + }, + annotations: { readOnlyHint: true, untrustedContentHint: true }, + async execute(input: Record) { + const q = String(input.query ?? '').trim(); + if (!q) return 'ERROR: `query` must be a non-empty string.'; + return dispatchAndWait('webmcp:search_tickets', { query: q }); + }, +}; +``` + +Key rules: +- **`execute()` wraps the existing UI code path** — dispatch the same event / call + the same store action / hit the same API the button does. Never a parallel + implementation. +- **Return only after the interface state is settled**: the component listener + awaits the real work, then fires the completion event with the outcome payload + (`{ ok, message | error }`) — full contract and component example in + `runtime.md`. A canned success before the work finishes is a false green. +- Return short strings; errors as `"ERROR: "` so the model can + self-correct. Cap outputs ~1.5k chars. +- Validate strictly in code, loosely in schema — and keep **parity with the + form's native HTML constraints**: when a tool wraps a form, probe the real + constraints on a detached clone instead of re-implementing them — + `const probe = emailInput.cloneNode() as HTMLInputElement; probe.value = value;` + then reject when `!probe.checkValidity()`. `execute()` must refuse exactly what + the form itself would refuse. + +### Registration & lifecycle + +- **Static registration is the default**: register app-wide tools once at bootstrap. +- **Per-view registration only** for tools meaningless outside their view — via + `createToolScope` in the view's mount/unmount (React `useEffect` cleanup, Vue + `onUnmounted`, Angular `DestroyRef`). Over-scoping makes the toolset flicker and + strands agents mid-plan. +- Registration failures roll back the scope and surface via `onError` — check the + console during integration; a silently missing toolset usually means a duplicate + name or invalid schema rejected the batch. + +### Auth / roles (SaaS) + +Never register a tool the current session couldn't use through the UI. On +login/logout/role change/tenant switch: dispose the scope and re-register the +correct set (`runtime.md` §Wiring). The server still re-checks everything (ground +rule 3) — role-scoped registration is UX hygiene, not security. + +## Origin trial / flags note + +WebMCP is a Chrome origin trial (149→, stable milestone still an estimate). For +production exposure the origin needs a token: +`` or an `Origin-Trial` response +header — registered at the Chrome Origin Trials console. For local work, +`chrome://flags/#enable-webmcp-testing`. Chrome **silently ignores** expired +tokens, so nothing may depend on WebMCP being present (ground rule 4). Add a short +note about this to the target repo's README as part of setup, and record the +touched file path in `pipeline.setup.originTrialNoted` (e.g. `["README.md"]`). diff --git a/skills/webmcpify/references/inventory.md b/skills/webmcpify/references/inventory.md new file mode 100644 index 000000000..bc3597249 --- /dev/null +++ b/skills/webmcpify/references/inventory.md @@ -0,0 +1,120 @@ +# Inventory — mapping a codebase into a tool manifest + +## Detect (Phase 0 details) + +Establish, in this order: + +1. **Stack**: `package.json` deps (react/vue/@angular/next/astro/eleventy…) or the + absence of one (static HTML). Record `app.stack` and `app.typescript`. +2. **Start command + base URL**: `dev`/`start` scripts, framework defaults + (`vite` → 5173, `next` → 3000, static → any file server). Verification needs a + working local run — if the app can't be started, append the blocker to + `pipeline.blockers` and surface it at the gate; don't silently proceed to a + verify phase that cannot run. +3. **Auth model**: none / session / role-based — plus **how a test session signs + in**, recorded per role under `app.authFixtures`: `obtain` (the exact steps — + seed command, login route), `account`, and `env` (the env var **names** the + fixture needs — never secret values in the manifest). The verify phase runs + from this. Role-based apps need role-scoped registration (`integrate.md` + §Auth) and a per-role verify pass. +4. **Git baseline**: `pipeline.baselineSha` = HEAD, `pipeline.baselineDirty` = + `git status --porcelain` paths. Dirty files are untouchable for the whole run. + +## Building the area map + +The area map is the unit of loop iteration. Sources, in order of preference: +router config (React Router, Next `app/`/`pages/`, Vue Router, Angular routes) → +navigation UI (static/SSG) → feature folders (`src/features/*`). Keep areas +coarse: 5–30 for a big SaaS, 1–3 for a landing page. Split an area that turns out +too big; merge trivial ones. + +## What counts as a candidate tool + +Walk each area's UI code and list **user actions**, not functions: + +| UI pattern | Candidate tool | `mutating` | `readOnlyHint` | +|---|---|---|---| +| Search/filter form or input | `search_` | false | true | +| Data list/detail currently rendered | `list_` / `get_` | false | true | +| Create/edit form with submit → API call | `create_` / `update_` | "server" | — | +| Button triggering a server state change | `_` | "server" | — | +| Preference/theme/localStorage toggle | `_` | "client" | — | +| Multi-step flow (wizard, checkout) | `start__flow` (initiation) | false* | **never** | +| Contact/booking form (static sites) | declarative form annotation | "server" | — | + +*Initiation tools only navigate/open the flow — the human completes it. They are +classified non-mutating (no data changes) **but must NOT carry `readOnlyHint`**: +they change UI state, and agents skip confirmations for hinted-read-only tools. +`readOnlyHint: true` is reserved for genuinely pure data reads. + +`mutating` is tri-state: `false` | `"client"` (browser-local only: prefs, theme, +localStorage — nothing leaves the browser) | `"server"` (data leaves the browser). +`"server"` gets the full ceremony — per-tool approval, required `cleanup`, +dev/test-data-only verification; `"client"` may be batch-approved at the gate +(`cleanup` recommended). `toolautosubmit` is banned for **both** mutation classes +(ground rule 5). + +**Skip** (do not inventory): login/logout/auth flows, payment execution, account +deletion, user management, anything irreversible, file uploads (v1), and pure +navigation agents can do anyway. + +## Tool budget, overlap, and priority (what keeps SaaS toolsets usable) + +Agents degrade when many similar tools compete. Enforce while drafting: + +- **Budget**: aim for ≤15 tools active in any app state (app-wide + current view). + If an area yields more candidates, keep the highest-value ones as `priority: 1` + and mark the rest `priority: 2/3` — the gate decides which waves ship. +- **Overlap rule**: no two tools whose descriptions could plausibly match the same + user request. Merge them (one tool, richer schema) or sharpen both descriptions + until they are disjoint. +- **Role/tenant coverage**: for role-scoped apps, note per tool which roles can use + it (`auth: ["role:", ...]`); the toolset a given session sees must stay + within budget too. + +## Naming and schema conventions (Google's, condensed) + +- **Verb-first, execution vs initiation honest**: `create_event` acts immediately; + `start_event_creation_process` merely opens a form. The name must never lie. +- Name ≤30 chars, `[a-zA-Z0-9_.-]`; prefix with the app name if tools may coexist + with other origins' tools in testing (`myapp_search_tickets`). +- Description ≤500 chars, positive capability statement, no marketing. Param + descriptions ≤150 chars. The description must say exactly what `execute()` does — + agents make consent decisions from it. +- **Raw user input rule**: schemas accept what the user would say ("11:00 to + 15:00"), never ask the agent to compute or transform. Semantic enum values + (`"High"`, not `priority_id: 3`). +- Tools returning user-generated or external content get + `untrustedContentHint: true`. + +## Choosing `kind` + +- `declarative` — any standard `
` whose fields map 1:1 to the action's + inputs: plain HTML, SSG-emitted, server-rendered, *and* framework-rendered forms + (uncontrolled inputs), including fetch-submitted forms (they bridge results via + `respondWith` — see `integrate.md`). +- `imperative` — non-form actions (buttons, drag/drop, selections), actions whose + inputs come from app state rather than form fields, and React/Vue **controlled** + forms (agent-driven fill would bypass the framework's state). + +## Writing manifest entries + +Fill EVERY field of the v3 schema: + +- `route` + `auth` (array of roles keying into `app.authFixtures`; verify runs + once per role). +- `annotations` — `readOnlyHint`/`untrustedContentHint` per the candidate table; + verify asserts them on the enumerated tool. +- `examples` — one valid + one invalid. `invalid: null` is allowed ONLY for + readOnly tools with no/empty params (verify then asserts dual-outcome); the + convention for a non-null invalid on zero-param tools is `{"unexpected": true}`. +- `expect` — exactly ONE of `result` (substring of the resolved string) or + `navigation` (destination URL/pattern when `executeTool` resolves `null`), + plus `ui` (a UI assertion a test can check). +- `cleanup` — required for `mutating: "server"`, recommended for `"client"`. + +The verify phase must be able to run from the manifest alone, without re-reading +the codebase — that is what makes runs resumable by a different agent. + +The completeness pass at the end of Phase 1: start the app (or read the rendered +nav), enumerate what a user can *do* per screen, and diff against the manifest. diff --git a/skills/webmcpify/references/runtime.md b/skills/webmcpify/references/runtime.md new file mode 100644 index 000000000..8e9728898 --- /dev/null +++ b/skills/webmcpify/references/runtime.md @@ -0,0 +1,129 @@ +# Runtime — vendoring and wiring the templates + +Copy from this skill's `templates/` directory into the target project +(suggested: `src/webmcp/`): + +- **TypeScript projects**: `templates/webmcpify.ts` + `templates/webmcp.d.ts`; + **React TSX projects additionally** `templates/webmcp-jsx.d.ts` (JSX typings for + the declarative attributes — a MODULE file; keep it separate from + `webmcp.d.ts`, which must stay a global script file). +- **JavaScript projects**: `templates/webmcpify.js` — **ES module only** (`export`): + load via a bundler or `