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 +21 -0
- package/README.md +207 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +47 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +29 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/suggest.d.ts.map +1 -0
- package/dist/commands/suggest.js +37 -0
- package/dist/commands/suggest.js.map +1 -0
- package/dist/commands/validate.d.ts.map +1 -0
- package/dist/commands/validate.js +39 -0
- package/dist/commands/validate.js.map +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/analyzer.d.ts.map +1 -0
- package/dist/lib/analyzer.js +177 -0
- package/dist/lib/analyzer.js.map +1 -0
- package/dist/lib/generator.d.ts.map +1 -0
- package/dist/lib/generator.js +136 -0
- package/dist/lib/generator.js.map +1 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +35 -0
- package/dist/lib/output.js.map +1 -0
- package/dist/lib/scanner.d.ts.map +1 -0
- package/dist/lib/scanner.js +270 -0
- package/dist/lib/scanner.js.map +1 -0
- package/dist/lib/validator.d.ts.map +1 -0
- package/dist/lib/validator.js +178 -0
- package/dist/lib/validator.js.map +1 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +61 -0
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
|
+
[](https://www.npmjs.com/package/wmcp-annotate)
|
|
6
|
+
[](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
|