Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ reconstruct them from git history.

## [Unreleased]

- Added an agent-launched, dependency-free visual WebMCP Workbench with the
WebMCPify palette, responsive desktop/phone layouts, manifest comparison,
schema-driven arguments, structured results, mutation confirmation, and
explicit `Native` versus `Simulated` evidence. The portable simulation is
development-only and never counts as native verification.
- Added native/stub I/O compat: the harness uses an explicit adapter mode —
stub `tool.execute(object)` or spec-shaped `mc.executeTool(object)` when
`mc.__webmcpStubObjectMode` is set, native `mc.executeTool(JSON string)` otherwise
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ Open your agent in the target repo and pick your scope:
/webmcpify # full pipeline
/webmcpify inventory # just investigate + propose the tool manifest (zero code changes)
/webmcpify integrate # integrate the approved manifest
/webmcpify workbench # agent launches the temporary visual tool inspector
/webmcpify verify # verify + heal what's integrated
/webmcpify status # where are we? what's next?
/webmcpify full parity # census every interactive element on every authenticated route
Expand Down Expand Up @@ -145,8 +146,8 @@ Every phase is a **loop over persistent state**, not a one-shot pass:
| Path | Purpose |
|---|---|
| [`skills/webmcpify/SKILL.md`](skills/webmcpify/SKILL.md) | The pipeline (what your agent follows) |
| [`skills/webmcpify/references/`](skills/webmcpify/references/) | Phase guides: inventory, integrate, runtime, verify, heal, security, discovery |
| [`skills/webmcpify/templates/`](skills/webmcpify/templates/) | Vendorable runtime (TS + JS), ambient types, Playwright verification template, discovery manifest |
| [`skills/webmcpify/references/`](skills/webmcpify/references/) | Phase guides: inventory, integrate, Workbench, runtime, verify, heal, security, discovery |
| [`skills/webmcpify/templates/`](skills/webmcpify/templates/) | Vendorable runtime (TS + JS), temporary visual Workbench, ambient types, Playwright verification template, discovery manifest |

## Status

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
},
"scripts": {
"typecheck": "tsc -p tsconfig.templates.json && tsc -p tsconfig.spec.json && tsc -p tsconfig.jsx.json",
"syntaxcheck": "node --check skills/webmcpify/templates/webmcpify.js",
"syntaxcheck": "node --check skills/webmcpify/templates/webmcpify.js && node --check skills/webmcpify/templates/webmcp-workbench.js && node --check skills/webmcpify/scripts/workbench.mjs",
"test": "node --test \"tests/*.test.mjs\"",
"check": "npm run typecheck && npm run syntaxcheck && npm test",
"workbench:verify": "node tests/workbench-browser.mjs",
"proof:verify": "xvfb-run -a node proof/demo/run.mjs --verify",
"proof:record": "xvfb-run -a node proof/demo/run.mjs --record"
},
Expand Down
24 changes: 22 additions & 2 deletions skills/webmcpify/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
name: webmcpify
description: WebMCP agent skill for curated core coverage or route-by-route parity — inventory an existing web app, integrate approved tools, then verify and heal them in a real browser. Use for "webmcpify", "add WebMCP", or "expose app actions to AI agents".
argument-hint: "[inventory|integrate|verify|status|full] [scope notes]"
description: WebMCP agent skill for curated core coverage or route-by-route parity — inventory an existing web app, integrate approved tools, then inspect, verify and heal them in a real browser. Use for "webmcpify", "add WebMCP", or "expose app actions to AI agents".
argument-hint: "[inventory|integrate|workbench|verify|status|full] [scope notes]"
license: MIT
tags:
- webmcp
Expand Down Expand Up @@ -43,6 +43,7 @@ The user may pass an argument (`/webmcpify <mode>` or plain words):
| *(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 |
| `workbench` / `inspect` | launch the temporary visual inspector for approved/integrated tools | user closes the session |
| `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 |

Expand Down Expand Up @@ -79,6 +80,9 @@ Any other text is scoping guidance (e.g. "only the checkout area", "read-only to
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.
8. **Workbench evidence is explicit.** The optional visual Workbench is development-
only and agent-launched (`references/workbench.md`). It must always label evidence
`Native` or `Simulated`; simulated calls never satisfy native verification.

## Fresh, authoritative guidance

Expand Down Expand Up @@ -367,6 +371,21 @@ a published manifest, and flag one **the pipeline created or modified** since
`baselineSha` as an unmapped hunk (a pre-existing, untouched manifest is not your
hunk — leave it alone).

## Optional — WORKBENCH (visual inspection)

When the user asks to inspect or try tools visually, read
`references/workbench.md` and launch the temporary Workbench yourself. Prefer its
Playwright runner: it injects before application code, requires no extension,
flag, command, or project edit from the user, and cleans up when the browser
closes. The panel must visibly say `Native` or `Simulated`.

Use the approved manifest as Expected evidence and the live page as Observed
evidence. Simulated mode is useful for portable browser/device and responsive
checks, but never changes a tool to `verified`; the normal headed native-browser
loop below remains authoritative. Never ship Workbench in a production entry or
bundle. If a physical device requires temporary dev-entry wiring, the agent adds
and removes it within the same inspection session.

## Phase 3 — VERIFY (loop)

Set up once from `templates/webmcp.spec.ts` per `references/verify.md` (real headed
Expand Down Expand Up @@ -439,6 +458,7 @@ scope collisions).
- `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/workbench.md` — zero-setup visual inspection and evidence modes
- `references/verify.md` — harness setup: flags, surfaces, Playwright/Puppeteer, evals
- `references/heal.md` — failure taxonomy → fixes
- `references/discovery.md` — optional off-page discovery (manifest, `rel="webmcp"`,
Expand Down
87 changes: 87 additions & 0 deletions skills/webmcpify/references/workbench.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# WebMCP Workbench — temporary visual inspection

Use the Workbench only after the manifest gate. It is a development aid, not
part of the application integration and not proof by itself.

## Evidence modes

- **Native** means the page already exposed a browser-provided
`document.modelContext` (or the deprecated `navigator.modelContext`) before
Workbench loaded. Calls use that implementation.
- **Simulated** means Workbench installed its dependency-free in-page transport
before the application entry. This previews registration, schemas, inputs,
execution, results, and responsive UI in browsers without WebMCP. It is never
recorded as native verification.

Run at least one Native session for a `verified` manifest status. A Simulated
session may diagnose UI/tool wiring and may support the browser/device matrix,
but it only produces portable-preview evidence.

## Zero-setup launch (preferred)

The agent owns starting and stopping the session. Do not ask the user to install
an extension, paste a snippet, set a browser flag, or run a command.

```sh
node <skill-dir>/scripts/workbench.mjs \
--url https://the-recorded-verification-origin.test \
--manifest .webmcpify/manifest.json \
--browser chromium
```

If the target has no Playwright installation, the launcher provisions a pinned
runtime in the operating system's temporary directory, outside the target repo.
If its requested portable browser engine is absent, it downloads that engine on
first launch. Native mode still requires a locally installed current Chrome.

Use `--native` for the current Chrome native surface. Use `--browser firefox` or
`--browser webkit` for portable checks when those Playwright engines are
available. The runner injects the inspector before application code and makes no
project changes. Close the launched browser (or interrupt the runner) to clean up.

## A physical device or an existing browser

When the user needs the exact device/browser rather than an agent-controlled
browser, the agent may temporarily load `templates/webmcp-workbench.js` before
the app entry in the development HTML and set:

```html
<script>
globalThis.__WEBMCPIFY_WORKBENCH__ = {
expectedTools: [/* approved manifest tools */],
open: true
};
</script>
<script src="/development-only/webmcp-workbench.js"></script>
```

The agent makes and later removes this temporary development-only wiring. Never
put it in a production entry, build, deployment, service worker, or dependency.
Keep the phone on the same approved development origin; the existing secure-
context and CORS gates still apply.

## Interaction contract

- The floating `w` launcher does not cover or dim the host application.
- Desktop uses an anchored panel. At 640px and below it becomes a safe-area-aware
full-screen inspector with a sticky Run action.
- Every session visibly says `Native` or `Simulated`.
- Read-only tools run directly. Server/client/unknown mutations require a
confirmation for every invocation.
- Results stay session-local. There is no chat, API key, bridge, export, snapshot,
persistence, or global invocation history in V1.
- Manifest valid/invalid examples are available as argument presets. The panel
keeps only its own 20 most recent calls in memory for the current session.
- Stop/destroy removes the UI and any Workbench-created simulated context.

## What to inspect

Compare Observed registration to the approved manifest: name, description,
parsed schema, mutation class, and annotations. Exercise valid and invalid
arguments. Verify the structured result, visible UI delta, navigation, and
cleanup exactly as required by `references/verify.md`.

For accessibility and responsive checks, cover keyboard-only operation, visible
focus, screen-reader labels/live results, reduced motion, light/dark preference,
360×800, 768×1024, and desktop. A portable check on several engines does not
replace the native Chrome verification.
111 changes: 111 additions & 0 deletions skills/webmcpify/scripts/workbench.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
#!/usr/bin/env node
import { spawn } from 'node:child_process';
import { mkdir, readFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

const here = dirname(fileURLToPath(import.meta.url));
const argv = process.argv.slice(2);
const playwrightVersion = '1.61.1';
const usage = 'Usage: node workbench.mjs --url <dev-url> [--manifest <path>] [--browser chromium|firefox|webkit] [--native]';
const value = (flag, fallback) => {
const index = argv.indexOf(flag);
if (index === -1) return fallback;
const result = argv[index + 1];
if (!result || result.startsWith('--')) throw new Error(`${flag} needs a value.`);
return result;
};
if (argv.includes('--help')) {
console.log(usage);
process.exit(0);
}
const url = value('--url');
if (!url) {
console.error(usage);
process.exit(2);
}
const browserName = value('--browser', 'chromium');
if (!['chromium', 'firefox', 'webkit'].includes(browserName)) throw new Error(`Unsupported browser "${browserName}".`);
const native = argv.includes('--native');
if (native && browserName !== 'chromium') throw new Error('--native requires Chromium/Chrome.');

function run(command, args, options = {}) {
return new Promise((resolveRun, rejectRun) => {
const child = spawn(command, args, { stdio: 'inherit', ...options });
child.once('error', rejectRun);
child.once('exit', (code, signal) => {
if (code === 0) resolveRun();
else rejectRun(new Error(`${command} exited with ${signal ? `signal ${signal}` : `code ${code}`}.`));
});
});
}

async function loadPlaywright() {
const localRequire = createRequire(import.meta.url);
try {
return { playwright: localRequire('playwright'), resolveModule: localRequire.resolve };
} catch (error) {
if (error?.code !== 'MODULE_NOT_FOUND' || !String(error.message).includes("'playwright'")) throw error;
}

const scratch = join(tmpdir(), `webmcpify-workbench-playwright-${playwrightVersion}`);
const scratchRequire = createRequire(join(scratch, 'package.json'));
try {
return { playwright: scratchRequire('playwright'), resolveModule: scratchRequire.resolve };
} catch (error) {
if (error?.code !== 'MODULE_NOT_FOUND' || !String(error.message).includes("'playwright'")) throw error;
}

await mkdir(scratch, { recursive: true });
console.error(`Preparing temporary Playwright ${playwrightVersion} runtime outside the target project…`);
await run(process.platform === 'win32' ? 'npm.cmd' : 'npm', [
'install', '--no-save', '--no-package-lock', `playwright@${playwrightVersion}`,
], { cwd: scratch, env: { ...process.env, PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' } });
return { playwright: scratchRequire('playwright'), resolveModule: scratchRequire.resolve };
}

const loaded = await loadPlaywright();
const { chromium, firefox, webkit } = loaded.playwright;
const browserType = { chromium, firefox, webkit }[browserName];

const manifestPath = value('--manifest');
const manifest = manifestPath ? JSON.parse(await readFile(resolve(manifestPath), 'utf8')) : { tools: [] };
const source = await readFile(resolve(here, '..', 'templates', 'webmcp-workbench.js'), 'utf8');
const bootSource = `globalThis.__WEBMCPIFY_WORKBENCH__ = ${JSON.stringify({ expectedTools: manifest.tools ?? [], open: true })};\n${source}`;
const launchOptions = {
headless: false,
...(native ? { channel: 'chrome', args: ['--enable-features=WebMCP,WebMCPTesting'] } : {}),
};
let browser;
try {
browser = await browserType.launch(launchOptions);
} catch (error) {
const missingBundledBrowser = !native && /Executable doesn't exist|browserType\.launch: Executable/.test(String(error));
if (!missingBundledBrowser) throw error;
console.error(`Downloading the temporary ${browserName} browser runtime…`);
await run(process.execPath, [loaded.resolveModule('playwright/cli'), 'install', browserName]);
browser = await browserType.launch(launchOptions);
}
try {
const context = await browser.newContext();
await context.addInitScript({ content: bootSource });
const page = await context.newPage();
page.on('console', (message) => {
if (message.type() === 'error') console.error(`[page] ${message.text()}`);
});
await page.goto(url, { waitUntil: 'domcontentloaded' });
const evidence = await page.locator('#webmcpify-workbench').getAttribute('data-evidence');
console.log(`WebMCP Workbench: ${url} · ${browserName} · ${evidence}`);
console.log('Close the browser or press Ctrl+C to end the temporary session.');

await new Promise((resolveDone) => {
const done = () => resolveDone();
browser.on('disconnected', done);
process.once('SIGINT', done);
process.once('SIGTERM', done);
});
} finally {
await browser.close().catch(() => {});
}
Loading
Loading