wmcp-annotate 1.0.3

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Satyan Avatara
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,207 @@
1
+ # wmcp-annotate
2
+
3
+ > The missing bridge between today's web and tomorrow's AI agents
4
+
5
+ [![npm version](https://badge.fury.io/js/wmcp-annotate.svg)](https://www.npmjs.com/package/wmcp-annotate)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ---
9
+
10
+ ## The WebMCP Moment
11
+
12
+ In February 2026, Google and Microsoft shipped WebMCP in Chrome 146 — a W3C standard that lets websites declare structured tools for AI agents.
13
+
14
+ This isn't incremental. It's architectural.
15
+
16
+ **Before WebMCP:** Agents scraped DOM (fragile) or parsed screenshots (expensive).
17
+ **After WebMCP:** Websites declare capabilities. Agents call tools directly.
18
+
19
+ The problem isn't the standard. It's adoption. **99% of websites have zero WebMCP annotations.**
20
+
21
+ That's why this exists.
22
+
23
+ ## Installation
24
+
25
+ ### Option 1: Install globally (recommended)
26
+
27
+ ```bash
28
+ npm install -g wmcp-annotate
29
+ wmcp-annotate scan https://example.com
30
+ ```
31
+
32
+ ### Option 2: Run directly with npx
33
+
34
+ ```bash
35
+ npx wmcp-annotate scan https://example.com
36
+ ```
37
+
38
+ ### Option 3: Run from GitHub (no npm publish needed)
39
+
40
+ ```bash
41
+ npx github:rsatyan/wmcp-annotate scan https://example.com
42
+ ```
43
+
44
+ > **Note:** Option 3 requires npm 10+. Check with `npm --version` and update with `npm install -g npm@latest` if needed.
45
+
46
+ **Zero additional setup required.** Works instantly for static HTML sites.
47
+
48
+ ## Quick Start
49
+
50
+ ```bash
51
+ # Scan a website (instant, no dependencies)
52
+ wmcp-annotate scan https://example.com
53
+
54
+ # Generate WebMCP tool definitions with AI
55
+ wmcp-annotate suggest https://example.com
56
+
57
+ # Output production-ready code
58
+ wmcp-annotate generate https://example.com --format typescript
59
+ ```
60
+
61
+ ## Commands
62
+
63
+ ### `scan` — Analyze a website
64
+
65
+ Discovers forms, buttons, links, and interactive elements.
66
+
67
+ ```bash
68
+ # Default: Fast HTML parsing (works for most sites)
69
+ wmcp-annotate scan https://example.com
70
+
71
+ # For JavaScript-heavy SPAs (requires Playwright setup)
72
+ wmcp-annotate scan https://react-app.com --browser
73
+ ```
74
+
75
+ **Output:**
76
+ ```json
77
+ {
78
+ "url": "https://example.com",
79
+ "elements": [
80
+ {
81
+ "type": "form",
82
+ "selector": "#search",
83
+ "label": "Search",
84
+ "inputs": [{ "name": "q", "type": "text" }]
85
+ }
86
+ ]
87
+ }
88
+ ```
89
+
90
+ ### `suggest` — Generate tool definitions
91
+
92
+ Uses AI to create meaningful WebMCP tool definitions from scan results.
93
+
94
+ ```bash
95
+ wmcp-annotate suggest https://example.com
96
+ wmcp-annotate suggest https://react-app.com --browser # For SPAs
97
+ wmcp-annotate suggest --scan-file scan.json --output tools.json
98
+ ```
99
+
100
+ Requires an AI provider (see [Configuration](#ai-provider-configuration)).
101
+
102
+ ### `generate` — Output production code
103
+
104
+ Creates ready-to-use JavaScript/TypeScript code.
105
+
106
+ ```bash
107
+ wmcp-annotate generate https://example.com --format js
108
+ wmcp-annotate generate https://example.com --format ts
109
+ wmcp-annotate generate https://example.com --format react
110
+ wmcp-annotate generate https://react-app.com --browser --format ts # For SPAs
111
+ ```
112
+
113
+ ### `validate` — Check compliance
114
+
115
+ Validates existing WebMCP implementations against the spec.
116
+
117
+ ```bash
118
+ wmcp-annotate validate https://example.com
119
+ wmcp-annotate validate https://example.com --ci # Exit 1 on issues
120
+ ```
121
+
122
+ ## AI Provider Configuration
123
+
124
+ The `suggest` command requires an AI provider. Configure one:
125
+
126
+ **OpenAI:**
127
+ ```bash
128
+ export OPENAI_API_KEY=sk-...
129
+ ```
130
+
131
+ **Anthropic:**
132
+ ```bash
133
+ export ANTHROPIC_API_KEY=sk-ant-...
134
+ npm install @anthropic-ai/sdk # Required for Anthropic
135
+ ```
136
+
137
+ **OpenAI-compatible APIs (Groq, Together, etc.):**
138
+ ```bash
139
+ export OPENAI_API_KEY=your_key
140
+ export OPENAI_BASE_URL=https://api.groq.com/openai
141
+ export WMCP_MODEL=llama-3.3-70b-versatile
142
+ ```
143
+
144
+ **Ollama (local, free):**
145
+ ```bash
146
+ export OLLAMA_HOST=http://localhost:11434
147
+ export WMCP_MODEL=llama3
148
+ ```
149
+
150
+ ## Browser Mode (for SPAs)
151
+
152
+ By default, `wmcp-annotate` uses fast HTML parsing which works for 80%+ of websites.
153
+
154
+ For JavaScript-heavy single-page apps (React, Vue, Angular), use `--browser` mode:
155
+
156
+ ```bash
157
+ # One-time setup
158
+ npm install playwright
159
+ npx playwright install chromium
160
+
161
+ # Scan with browser engine
162
+ wmcp-annotate scan https://react-app.com --browser
163
+ ```
164
+
165
+ ## Example Output
166
+
167
+ Generated code is fully compliant with the [WebMCP spec](https://webmachinelearning.github.io/webmcp/):
168
+
169
+ ```typescript
170
+ navigator.modelContext.registerTool({
171
+ name: "searchProducts",
172
+ description: "Search the product catalog by keyword",
173
+ inputSchema: {
174
+ type: "object",
175
+ properties: {
176
+ query: { type: "string", description: "Search terms" }
177
+ },
178
+ required: ["query"]
179
+ },
180
+ annotations: { readOnlyHint: true },
181
+ async execute({ query }, client) {
182
+ const form = document.querySelector('#search-form');
183
+ form.querySelector('input[name="q"]').value = query;
184
+ form.submit();
185
+ return {
186
+ content: [{ type: "text", text: JSON.stringify({ success: true }) }]
187
+ };
188
+ }
189
+ });
190
+ ```
191
+
192
+ ## Why Open Source?
193
+
194
+ WebMCP adoption benefits everyone building AI agents. Gatekeeping the tooling slows the ecosystem.
195
+
196
+ This tool is MIT licensed. Fork it. Ship it. Make it better.
197
+
198
+ ## Author
199
+
200
+ **Satyan Avatara**
201
+ 📧 rsatyan@gmail.com
202
+
203
+ Building at the intersection of AI, web infrastructure, and financial services.
204
+
205
+ ## License
206
+
207
+ MIT — use it, fork it, ship it.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.d.ts","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAiB,MAAM,aAAa,CAAC;AAQlE,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAwCtG"}
@@ -0,0 +1,47 @@
1
+ import { generator } from '../lib/generator.js';
2
+ import { analyzer } from '../lib/analyzer.js';
3
+ import { scanner } from '../lib/scanner.js';
4
+ import { readInput } from '../lib/output.js';
5
+ import ora from 'ora';
6
+ import chalk from 'chalk';
7
+ export async function generateCommand(url, options) {
8
+ const spinner = ora('Generating...').start();
9
+ try {
10
+ // Get suggestions (from file or live analysis)
11
+ let suggestions;
12
+ if (options.suggestFile) {
13
+ spinner.text = 'Loading suggestions file...';
14
+ suggestions = await readInput(options.suggestFile);
15
+ }
16
+ else if (url) {
17
+ const mode = options.browser ? 'browser' : 'static HTML';
18
+ spinner.text = `Scanning ${url} (${mode})...`;
19
+ const scanResult = await scanner.scan(url, { depth: 1, browser: options.browser });
20
+ spinner.text = 'Generating tool suggestions...';
21
+ suggestions = await analyzer.suggest(scanResult);
22
+ }
23
+ else {
24
+ throw new Error('Either URL or --suggest-file is required');
25
+ }
26
+ spinner.text = `Generating ${options.format.toUpperCase()} code...`;
27
+ const code = await generator.generate(suggestions, {
28
+ format: options.format,
29
+ module: options.module,
30
+ });
31
+ spinner.succeed(`Generated code for ${suggestions.tools.length} tools`);
32
+ if (options.output) {
33
+ const fs = await import('fs/promises');
34
+ await fs.writeFile(options.output, code, 'utf-8');
35
+ console.log(chalk.green(`\\nSaved to ${options.output}`));
36
+ }
37
+ else {
38
+ console.log('\\n' + code);
39
+ }
40
+ }
41
+ catch (error) {
42
+ spinner.fail('Generation failed');
43
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
44
+ process.exit(1);
45
+ }
46
+ }
47
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generate.js","sourceRoot":"","sources":["../../src/commands/generate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAe,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAuB,EAAE,OAAwB;IACrF,MAAM,OAAO,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC,KAAK,EAAE,CAAC;IAE7C,IAAI,CAAC;QACH,+CAA+C;QAC/C,IAAI,WAA0B,CAAC;QAC/B,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;YACxB,OAAO,CAAC,IAAI,GAAG,6BAA6B,CAAC;YAC7C,WAAW,GAAG,MAAM,SAAS,CAAgB,OAAO,CAAC,WAAW,CAAC,CAAC;QACpE,CAAC;aAAM,IAAI,GAAG,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;YACzD,OAAO,CAAC,IAAI,GAAG,YAAY,GAAG,KAAK,IAAI,MAAM,CAAC;YAC9C,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;YAEnF,OAAO,CAAC,IAAI,GAAG,gCAAgC,CAAC;YAChD,WAAW,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACnD,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,0CAA0C,CAAC,CAAC;QAC9D,CAAC;QAED,OAAO,CAAC,IAAI,GAAG,cAAc,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,UAAU,CAAC;QACpE,MAAM,IAAI,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,WAAW,EAAE;YACjD,MAAM,EAAE,OAAO,CAAC,MAAuC;YACvD,MAAM,EAAE,OAAO,CAAC,MAAuB;SACxC,CAAC,CAAC;QAEH,OAAO,CAAC,OAAO,CAAC,sBAAsB,WAAW,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;QAExE,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;YACvC,MAAM,EAAE,CAAC,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC;YAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAC;QAC5B,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.d.ts","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAc,MAAM,aAAa,CAAC;AAM3D,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BlF"}
@@ -0,0 +1,29 @@
1
+ import { scanner } from '../lib/scanner.js';
2
+ import { writeOutput } from '../lib/output.js';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ export async function scanCommand(url, options) {
6
+ const mode = options.browser ? 'browser' : 'static HTML';
7
+ const spinner = ora(`Scanning ${url} (${mode})...`).start();
8
+ try {
9
+ const result = await scanner.scan(url, {
10
+ depth: parseInt(options.depth, 10),
11
+ verbose: options.verbose,
12
+ browser: options.browser,
13
+ });
14
+ spinner.succeed(`Found ${result.elements.length} elements${result.apiCalls.length ? ` and ${result.apiCalls.length} API calls` : ''}`);
15
+ await writeOutput(result, options);
16
+ if (!options.output) {
17
+ console.log(chalk.dim('\nUse --output to save results to a file'));
18
+ }
19
+ if (!options.browser && result.elements.length === 0) {
20
+ console.log(chalk.yellow('\nTip: If this site uses JavaScript rendering, try: --browser'));
21
+ }
22
+ }
23
+ catch (error) {
24
+ spinner.fail('Scan failed');
25
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
26
+ process.exit(1);
27
+ }
28
+ }
29
+ //# sourceMappingURL=scan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scan.js","sourceRoot":"","sources":["../../src/commands/scan.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,GAAW,EAAE,OAAoB;IACjE,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;IACzD,MAAM,OAAO,GAAG,GAAG,CAAC,YAAY,GAAG,KAAK,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IAE5D,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;YACrC,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;YAClC,OAAO,EAAE,OAAO,CAAC,OAAO;YACxB,OAAO,EAAE,OAAO,CAAC,OAAO;SACzB,CAAC,CAAC;QAEH,OAAO,CAAC,OAAO,CAAC,SAAS,MAAM,CAAC,QAAQ,CAAC,MAAM,YAAY,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,MAAM,CAAC,QAAQ,CAAC,MAAM,YAAY,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAEvI,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEnC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC,CAAC;QACrE,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,OAAO,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,+DAA+D,CAAC,CAAC,CAAC;QAC7F,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.d.ts","sourceRoot":"","sources":["../../src/commands/suggest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAA6B,MAAM,aAAa,CAAC;AAO7E,wBAAsB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCpG"}
@@ -0,0 +1,37 @@
1
+ import { analyzer } from '../lib/analyzer.js';
2
+ import { scanner } from '../lib/scanner.js';
3
+ import { writeOutput, readInput } from '../lib/output.js';
4
+ import ora from 'ora';
5
+ import chalk from 'chalk';
6
+ export async function suggestCommand(url, options) {
7
+ const spinner = ora('Analyzing...').start();
8
+ try {
9
+ // Get scan results (from file or live scan)
10
+ let scanResult;
11
+ if (options.scanFile) {
12
+ spinner.text = 'Loading scan file...';
13
+ scanResult = await readInput(options.scanFile);
14
+ }
15
+ else if (url) {
16
+ const mode = options.browser ? 'browser' : 'static HTML';
17
+ spinner.text = `Scanning ${url} (${mode})...`;
18
+ scanResult = await scanner.scan(url, { depth: 1, browser: options.browser });
19
+ }
20
+ else {
21
+ throw new Error('Either URL or --scan-file is required');
22
+ }
23
+ spinner.text = 'Generating tool suggestions with AI...';
24
+ const result = await analyzer.suggest(scanResult);
25
+ spinner.succeed(`Generated ${result.tools.length} tool suggestions`);
26
+ await writeOutput(result, options);
27
+ if (!options.output) {
28
+ console.log(chalk.dim('\\nUse --output to save results to a file'));
29
+ }
30
+ }
31
+ catch (error) {
32
+ spinner.fail('Suggestion failed');
33
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
34
+ process.exit(1);
35
+ }
36
+ }
37
+ //# sourceMappingURL=suggest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest.js","sourceRoot":"","sources":["../../src/commands/suggest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,GAAuB,EAAE,OAAuB;IACnF,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC,KAAK,EAAE,CAAC;IAE5C,IAAI,CAAC;QACH,4CAA4C;QAC5C,IAAI,UAAsB,CAAC;QAC3B,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,GAAG,sBAAsB,CAAC;YACtC,UAAU,GAAG,MAAM,SAAS,CAAa,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC7D,CAAC;aAAM,IAAI,GAAG,EAAE,CAAC;YACf,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC;YACzD,OAAO,CAAC,IAAI,GAAG,YAAY,GAAG,KAAK,IAAI,MAAM,CAAC;YAC9C,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC;QAC/E,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC;QAC3D,CAAC;QAED,OAAO,CAAC,IAAI,GAAG,wCAAwC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAElD,OAAO,CAAC,OAAO,CAAC,aAAa,MAAM,CAAC,KAAK,CAAC,MAAM,mBAAmB,CAAC,CAAC;QAErE,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEnC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC,CAAC;QACtE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAoB,MAAM,aAAa,CAAC;AAMrE,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAqC1F"}
@@ -0,0 +1,39 @@
1
+ import { validator } from '../lib/validator.js';
2
+ import { writeOutput } from '../lib/output.js';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ export async function validateCommand(url, options) {
6
+ const spinner = ora(`Validating ${url}...`).start();
7
+ try {
8
+ const result = await validator.validate(url);
9
+ const { summary } = result;
10
+ if (summary.errors > 0) {
11
+ spinner.fail(`Validation failed: ${summary.errors} errors, ${summary.warnings} warnings`);
12
+ }
13
+ else if (summary.warnings > 0) {
14
+ spinner.warn(`Validation passed with ${summary.warnings} warnings`);
15
+ }
16
+ else {
17
+ spinner.succeed(`Validation passed: ${summary.total} tools validated`);
18
+ }
19
+ await writeOutput(result, options);
20
+ // Handle CI mode
21
+ if (options.ci) {
22
+ if (summary.errors > 0 || (options.strict && summary.warnings > 0)) {
23
+ process.exit(1);
24
+ }
25
+ }
26
+ // Print summary
27
+ console.log('\\n' + chalk.bold('Summary:'));
28
+ console.log(` Total tools: ${summary.total}`);
29
+ console.log(` Valid: ${chalk.green(summary.valid)}`);
30
+ console.log(` Warnings: ${chalk.yellow(summary.warnings)}`);
31
+ console.log(` Errors: ${chalk.red(summary.errors)}`);
32
+ }
33
+ catch (error) {
34
+ spinner.fail('Validation failed');
35
+ console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
36
+ process.exit(1);
37
+ }
38
+ }
39
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/commands/validate.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW,EAAE,OAAwB;IACzE,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,GAAG,KAAK,CAAC,CAAC,KAAK,EAAE,CAAC;IAEpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;QAE7C,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;QAE3B,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,sBAAsB,OAAO,CAAC,MAAM,YAAY,OAAO,CAAC,QAAQ,WAAW,CAAC,CAAC;QAC5F,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC,EAAE,CAAC;YAChC,OAAO,CAAC,IAAI,CAAC,0BAA0B,OAAO,CAAC,QAAQ,WAAW,CAAC,CAAC;QACtE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,OAAO,CAAC,sBAAsB,OAAO,CAAC,KAAK,kBAAkB,CAAC,CAAC;QACzE,CAAC;QAED,MAAM,WAAW,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAEnC,iBAAiB;QACjB,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;YACf,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC,EAAE,CAAC;gBACnE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QAED,gBAAgB;QAChB,OAAO,CAAC,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;QAC5C,OAAO,CAAC,GAAG,CAAC,kBAAkB,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAC/C,OAAO,CAAC,GAAG,CAAC,YAAY,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,eAAe,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QAC7D,OAAO,CAAC,GAAG,CAAC,aAAa,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IAExD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;QACnF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { scanCommand } from './commands/scan.js';
4
+ import { suggestCommand } from './commands/suggest.js';
5
+ import { generateCommand } from './commands/generate.js';
6
+ import { validateCommand } from './commands/validate.js';
7
+ const program = new Command();
8
+ program
9
+ .name('wmcp-annotate')
10
+ .description('Make any website AI-agent ready with WebMCP annotations')
11
+ .version('1.0.3');
12
+ program
13
+ .command('scan <url>')
14
+ .description('Analyze a website for WebMCP opportunities')
15
+ .option('-d, --depth <number>', 'How many pages deep to crawl', '1')
16
+ .option('-o, --output <file>', 'Output file')
17
+ .option('-f, --format <format>', 'Output format: json, table, markdown', 'json')
18
+ .option('-b, --browser', 'Use browser engine for JavaScript-heavy sites (requires Playwright)')
19
+ .option('-v, --verbose', 'Show detailed progress')
20
+ .action(scanCommand);
21
+ program
22
+ .command('suggest [url]')
23
+ .description('Generate AI-powered WebMCP tool suggestions')
24
+ .option('-s, --scan-file <file>', 'Use existing scan output')
25
+ .option('-o, --output <file>', 'Output file')
26
+ .option('-f, --format <format>', 'Output format: json, yaml', 'json')
27
+ .option('-b, --browser', 'Use browser engine for JavaScript-heavy sites (requires Playwright)')
28
+ .action(suggestCommand);
29
+ program
30
+ .command('generate [url]')
31
+ .description('Generate WebMCP registration code')
32
+ .option('-s, --suggest-file <file>', 'Use existing suggestions')
33
+ .option('-o, --output <file>', 'Output file')
34
+ .option('-f, --format <format>', 'Output format: js, ts, react, vue', 'js')
35
+ .option('-m, --module <type>', 'Module format: esm, cjs', 'esm')
36
+ .option('-b, --browser', 'Use browser engine for JavaScript-heavy sites (requires Playwright)')
37
+ .action(generateCommand);
38
+ program
39
+ .command('validate <url>')
40
+ .description('Validate WebMCP implementation')
41
+ .option('-o, --output <file>', 'Output file')
42
+ .option('-f, --format <format>', 'Output format: json, table, markdown', 'json')
43
+ .option('--strict', 'Fail on warnings')
44
+ .option('--ci', 'Exit code 1 on any issues')
45
+ .action(validateCommand);
46
+ program.parse();
47
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,eAAe,CAAC;KACrB,WAAW,CAAC,yDAAyD,CAAC;KACtE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,YAAY,CAAC;KACrB,WAAW,CAAC,4CAA4C,CAAC;KACzD,MAAM,CAAC,sBAAsB,EAAE,8BAA8B,EAAE,GAAG,CAAC;KACnE,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,uBAAuB,EAAE,sCAAsC,EAAE,MAAM,CAAC;KAC/E,MAAM,CAAC,eAAe,EAAE,qEAAqE,CAAC;KAC9F,MAAM,CAAC,eAAe,EAAE,wBAAwB,CAAC;KACjD,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,eAAe,CAAC;KACxB,WAAW,CAAC,6CAA6C,CAAC;KAC1D,MAAM,CAAC,wBAAwB,EAAE,0BAA0B,CAAC;KAC5D,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,uBAAuB,EAAE,2BAA2B,EAAE,MAAM,CAAC;KACpE,MAAM,CAAC,eAAe,EAAE,qEAAqE,CAAC;KAC9F,MAAM,CAAC,cAAc,CAAC,CAAC;AAE1B,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,2BAA2B,EAAE,0BAA0B,CAAC;KAC/D,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,uBAAuB,EAAE,mCAAmC,EAAE,IAAI,CAAC;KAC1E,MAAM,CAAC,qBAAqB,EAAE,yBAAyB,EAAE,KAAK,CAAC;KAC/D,MAAM,CAAC,eAAe,EAAE,qEAAqE,CAAC;KAC9F,MAAM,CAAC,eAAe,CAAC,CAAC;AAE3B,OAAO;KACJ,OAAO,CAAC,gBAAgB,CAAC;KACzB,WAAW,CAAC,gCAAgC,CAAC;KAC7C,MAAM,CAAC,qBAAqB,EAAE,aAAa,CAAC;KAC5C,MAAM,CAAC,uBAAuB,EAAE,sCAAsC,EAAE,MAAM,CAAC;KAC/E,MAAM,CAAC,UAAU,EAAE,kBAAkB,CAAC;KACtC,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC;KAC3C,MAAM,CAAC,eAAe,CAAC,CAAC;AAE3B,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyzer.d.ts","sourceRoot":"","sources":["../../src/lib/analyzer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAkB,MAAM,aAAa,CAAC;AAW7E,cAAM,QAAQ;IACZ,OAAO,CAAC,MAAM,CAAyB;IAEvC,OAAO,CAAC,SAAS;IA2CX,OAAO,CAAC,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC;YAkB/C,cAAc;YAqDd,MAAM;YAaN,aAAa;YA0Bb,UAAU;YAyBV,UAAU;CAoBzB;AAED,eAAO,MAAM,QAAQ,UAAiB,CAAC"}
@@ -0,0 +1,177 @@
1
+ class Analyzer {
2
+ config = null;
3
+ getConfig() {
4
+ if (this.config)
5
+ return this.config;
6
+ // Check for Anthropic
7
+ if (process.env.ANTHROPIC_API_KEY) {
8
+ this.config = {
9
+ provider: 'anthropic',
10
+ apiKey: process.env.ANTHROPIC_API_KEY,
11
+ model: process.env.WMCP_MODEL || 'claude-sonnet-4-20250514',
12
+ };
13
+ return this.config;
14
+ }
15
+ // Check for OpenAI
16
+ if (process.env.OPENAI_API_KEY) {
17
+ this.config = {
18
+ provider: 'openai',
19
+ apiKey: process.env.OPENAI_API_KEY,
20
+ model: process.env.WMCP_MODEL || 'gpt-4o',
21
+ baseUrl: process.env.OPENAI_BASE_URL,
22
+ };
23
+ return this.config;
24
+ }
25
+ // Check for Ollama (local)
26
+ if (process.env.OLLAMA_HOST || process.env.WMCP_OLLAMA) {
27
+ this.config = {
28
+ provider: 'ollama',
29
+ model: process.env.WMCP_MODEL || 'llama3',
30
+ baseUrl: process.env.OLLAMA_HOST || 'http://localhost:11434',
31
+ };
32
+ return this.config;
33
+ }
34
+ throw new Error('No AI provider configured. Set one of:\n' +
35
+ ' - ANTHROPIC_API_KEY (for Claude)\n' +
36
+ ' - OPENAI_API_KEY (for GPT-4, or any OpenAI-compatible API)\n' +
37
+ ' - OLLAMA_HOST (for local Ollama)\n' +
38
+ '\nOptionally set WMCP_MODEL to specify the model.');
39
+ }
40
+ async suggest(scanResult) {
41
+ const tools = [];
42
+ for (const element of scanResult.elements) {
43
+ const suggestion = await this.analyzeElement(element);
44
+ if (suggestion) {
45
+ tools.push(suggestion);
46
+ }
47
+ }
48
+ return {
49
+ version: '1.0.0',
50
+ url: scanResult.url,
51
+ suggestedAt: new Date().toISOString(),
52
+ tools,
53
+ };
54
+ }
55
+ async analyzeElement(element) {
56
+ const config = this.getConfig();
57
+ const prompt = `Analyze this website element and generate a WebMCP tool definition.
58
+
59
+ Element:
60
+ - Type: ${element.type}
61
+ - Label: ${element.label}
62
+ - Selector: ${element.selector}
63
+ ${element.inputs ? `- Inputs: ${JSON.stringify(element.inputs)}` : ''}
64
+
65
+ Generate a WebMCP tool definition with:
66
+ 1. name: camelCase, action-oriented verb (e.g., searchProducts, addToCart)
67
+ 2. description: 1-2 sentences explaining what it does
68
+ 3. readOnly: true if it's a search/view action, false for create/update/delete
69
+ 4. inputSchema: JSON Schema for the inputs
70
+
71
+ Respond with valid JSON only:
72
+ {
73
+ "name": "...",
74
+ "description": "...",
75
+ "readOnly": true/false,
76
+ "inputSchema": { ... }
77
+ }`;
78
+ try {
79
+ const responseText = await this.callAI(config, prompt);
80
+ // Extract JSON from response (handle markdown code blocks)
81
+ let jsonStr = responseText;
82
+ const jsonMatch = responseText.match(/```(?:json)?\s*([\s\S]*?)```/);
83
+ if (jsonMatch) {
84
+ jsonStr = jsonMatch[1];
85
+ }
86
+ const json = JSON.parse(jsonStr.trim());
87
+ return {
88
+ name: json.name,
89
+ description: json.description,
90
+ readOnly: json.readOnly,
91
+ inputSchema: json.inputSchema,
92
+ sourceElement: {
93
+ type: element.type,
94
+ selector: element.selector,
95
+ },
96
+ };
97
+ }
98
+ catch (error) {
99
+ console.error('Analysis failed for element:', element.selector, error);
100
+ return null;
101
+ }
102
+ }
103
+ async callAI(config, prompt) {
104
+ switch (config.provider) {
105
+ case 'anthropic':
106
+ return this.callAnthropic(config, prompt);
107
+ case 'openai':
108
+ return this.callOpenAI(config, prompt);
109
+ case 'ollama':
110
+ return this.callOllama(config, prompt);
111
+ default:
112
+ throw new Error(`Unknown provider: ${config.provider}`);
113
+ }
114
+ }
115
+ async callAnthropic(config, prompt) {
116
+ // Dynamic import to avoid requiring the SDK if not using Anthropic
117
+ let Anthropic;
118
+ try {
119
+ const module = await import('@anthropic-ai/sdk');
120
+ Anthropic = module.default;
121
+ }
122
+ catch {
123
+ throw new Error('Anthropic SDK not installed. Install it with:\n' +
124
+ ' npm install @anthropic-ai/sdk');
125
+ }
126
+ const client = new Anthropic({ apiKey: config.apiKey });
127
+ const response = await client.messages.create({
128
+ model: config.model,
129
+ max_tokens: 1024,
130
+ messages: [{ role: 'user', content: prompt }],
131
+ });
132
+ const content = response.content[0];
133
+ if (content.type !== 'text')
134
+ throw new Error('Unexpected response type');
135
+ return content.text;
136
+ }
137
+ async callOpenAI(config, prompt) {
138
+ const url = `${config.baseUrl || 'https://api.openai.com'}/v1/chat/completions`;
139
+ const response = await fetch(url, {
140
+ method: 'POST',
141
+ headers: {
142
+ 'Content-Type': 'application/json',
143
+ 'Authorization': `Bearer ${config.apiKey}`,
144
+ },
145
+ body: JSON.stringify({
146
+ model: config.model,
147
+ messages: [{ role: 'user', content: prompt }],
148
+ max_tokens: 1024,
149
+ }),
150
+ });
151
+ if (!response.ok) {
152
+ const error = await response.text();
153
+ throw new Error(`OpenAI API error: ${response.status} ${error}`);
154
+ }
155
+ const data = await response.json();
156
+ return data.choices[0].message.content;
157
+ }
158
+ async callOllama(config, prompt) {
159
+ const url = `${config.baseUrl}/api/generate`;
160
+ const response = await fetch(url, {
161
+ method: 'POST',
162
+ headers: { 'Content-Type': 'application/json' },
163
+ body: JSON.stringify({
164
+ model: config.model,
165
+ prompt: prompt,
166
+ stream: false,
167
+ }),
168
+ });
169
+ if (!response.ok) {
170
+ throw new Error(`Ollama API error: ${response.statusText}`);
171
+ }
172
+ const data = await response.json();
173
+ return data.response;
174
+ }
175
+ }
176
+ export const analyzer = new Analyzer();
177
+ //# sourceMappingURL=analyzer.js.map