wmcp-annotate 1.0.0
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.
Potentially problematic release.
This version of wmcp-annotate might be problematic. Click here for more details.
- package/AGENTS.md +108 -0
- package/IMPLEMENTATION_PLAN.md +187 -0
- package/LAUNCH.md +217 -0
- package/PRD.md +199 -0
- package/PROMPT.md +62 -0
- package/README.md +140 -0
- package/dist/commands/generate.d.ts +3 -0
- package/dist/commands/generate.d.ts.map +1 -0
- package/dist/commands/generate.js +46 -0
- package/dist/commands/generate.js.map +1 -0
- package/dist/commands/scan.d.ts +3 -0
- package/dist/commands/scan.d.ts.map +1 -0
- package/dist/commands/scan.js +24 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/suggest.d.ts +3 -0
- package/dist/commands/suggest.d.ts.map +1 -0
- package/dist/commands/suggest.js +36 -0
- package/dist/commands/suggest.js.map +1 -0
- package/dist/commands/validate.d.ts +3 -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 +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +44 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/analyzer.d.ts +10 -0
- package/dist/lib/analyzer.d.ts.map +1 -0
- package/dist/lib/analyzer.js +80 -0
- package/dist/lib/analyzer.js.map +1 -0
- package/dist/lib/generator.d.ts +12 -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 +6 -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 +19 -0
- package/dist/lib/scanner.d.ts.map +1 -0
- package/dist/lib/scanner.js +159 -0
- package/dist/lib/scanner.js.map +1 -0
- package/dist/lib/validator.d.ts +13 -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 +109 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/docs/index.html +563 -0
- package/marketing/email-outreach.md +183 -0
- package/marketing/landing-page.md +165 -0
- package/marketing/social-posts.md +192 -0
- package/package.json +58 -0
- package/scripts/publish.sh +33 -0
- package/specs/generate-command.md +147 -0
- package/specs/scan-command.md +92 -0
- package/specs/suggest-command.md +120 -0
- package/specs/validate-command.md +108 -0
- package/src/commands/generate.ts +48 -0
- package/src/commands/scan.ts +28 -0
- package/src/commands/suggest.ts +39 -0
- package/src/commands/validate.ts +44 -0
- package/src/index.ts +51 -0
- package/src/lib/analyzer.ts +90 -0
- package/src/lib/generator.ts +149 -0
- package/src/lib/output.ts +40 -0
- package/src/lib/scanner.ts +185 -0
- package/src/lib/validator.ts +192 -0
- package/src/types.ts +124 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Spec: Generate Command
|
|
2
|
+
|
|
3
|
+
## JTBD
|
|
4
|
+
As a developer, I want to generate ready-to-use JavaScript/TypeScript code that implements WebMCP tool registrations, so I can copy-paste it into my project.
|
|
5
|
+
|
|
6
|
+
## Command
|
|
7
|
+
```bash
|
|
8
|
+
wmcp-annotate generate <url> [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--suggest-file, -s Use existing suggestions instead of live analysis
|
|
12
|
+
--output, -o Output file
|
|
13
|
+
--format, -f Output format: js, ts, react, vue, svelte
|
|
14
|
+
--module, -m Module format: esm, cjs
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Behavior
|
|
18
|
+
|
|
19
|
+
### Input
|
|
20
|
+
- URL to analyze (or existing suggestions file)
|
|
21
|
+
- Output format preference
|
|
22
|
+
|
|
23
|
+
### Process
|
|
24
|
+
1. Run suggest (or load suggestions file)
|
|
25
|
+
2. For each tool:
|
|
26
|
+
- Generate registration code
|
|
27
|
+
- Add execute handler stub
|
|
28
|
+
- Include comments with implementation hints
|
|
29
|
+
3. Wrap in appropriate module format
|
|
30
|
+
4. Add TypeScript types if ts format
|
|
31
|
+
|
|
32
|
+
### Output Examples
|
|
33
|
+
|
|
34
|
+
#### JavaScript (ESM)
|
|
35
|
+
```javascript
|
|
36
|
+
// WebMCP Tool Registration
|
|
37
|
+
// Generated by wmcp-annotate
|
|
38
|
+
// URL: https://example.com
|
|
39
|
+
|
|
40
|
+
// Tool: searchProducts
|
|
41
|
+
// Search for products by keyword, category, or filters
|
|
42
|
+
navigator.modelContext.registerTool({
|
|
43
|
+
name: "searchProducts",
|
|
44
|
+
description: "Search for products by keyword, category, or filters",
|
|
45
|
+
readOnly: true,
|
|
46
|
+
inputSchema: {
|
|
47
|
+
type: "object",
|
|
48
|
+
properties: {
|
|
49
|
+
query: {
|
|
50
|
+
type: "string",
|
|
51
|
+
description: "Search keywords"
|
|
52
|
+
},
|
|
53
|
+
category: {
|
|
54
|
+
type: "string",
|
|
55
|
+
enum: ["electronics", "clothing", "home"]
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
required: ["query"]
|
|
59
|
+
},
|
|
60
|
+
async execute({ query, category }) {
|
|
61
|
+
// TODO: Implement search logic
|
|
62
|
+
// Suggested implementation:
|
|
63
|
+
// 1. Find form: document.querySelector('#search-form')
|
|
64
|
+
// 2. Fill inputs with provided values
|
|
65
|
+
// 3. Submit form or make API call
|
|
66
|
+
// 4. Return results
|
|
67
|
+
|
|
68
|
+
const form = document.querySelector('#search-form');
|
|
69
|
+
const queryInput = form.querySelector('input[name="query"]');
|
|
70
|
+
const categorySelect = form.querySelector('select[name="category"]');
|
|
71
|
+
|
|
72
|
+
queryInput.value = query;
|
|
73
|
+
if (category) categorySelect.value = category;
|
|
74
|
+
|
|
75
|
+
// Trigger search
|
|
76
|
+
form.dispatchEvent(new Event('submit'));
|
|
77
|
+
|
|
78
|
+
// Wait for results (implement based on your app)
|
|
79
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
80
|
+
|
|
81
|
+
const results = document.querySelectorAll('.product-item');
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text: JSON.stringify(Array.from(results).map(el => ({
|
|
86
|
+
name: el.querySelector('.name')?.textContent,
|
|
87
|
+
price: el.querySelector('.price')?.textContent
|
|
88
|
+
})))
|
|
89
|
+
}]
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### TypeScript
|
|
96
|
+
```typescript
|
|
97
|
+
// WebMCP Tool Registration
|
|
98
|
+
// Generated by wmcp-annotate
|
|
99
|
+
|
|
100
|
+
interface SearchProductsInput {
|
|
101
|
+
query: string;
|
|
102
|
+
category?: "electronics" | "clothing" | "home";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
navigator.modelContext.registerTool({
|
|
106
|
+
name: "searchProducts",
|
|
107
|
+
description: "Search for products by keyword, category, or filters",
|
|
108
|
+
readOnly: true,
|
|
109
|
+
inputSchema: { /* ... */ },
|
|
110
|
+
async execute(input: SearchProductsInput) {
|
|
111
|
+
// Implementation
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### React Hook
|
|
117
|
+
```typescript
|
|
118
|
+
// useWebMCPTools.ts
|
|
119
|
+
import { useEffect } from 'react';
|
|
120
|
+
|
|
121
|
+
export function useWebMCPTools() {
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (!navigator.modelContext) return;
|
|
124
|
+
|
|
125
|
+
const cleanup = navigator.modelContext.registerTool({
|
|
126
|
+
name: "searchProducts",
|
|
127
|
+
// ...
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
return () => cleanup?.();
|
|
131
|
+
}, []);
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Tests
|
|
136
|
+
1. Generate JS → valid JavaScript that runs
|
|
137
|
+
2. Generate TS → valid TypeScript that compiles
|
|
138
|
+
3. Generate React → valid React hook
|
|
139
|
+
4. Multiple tools → all registered
|
|
140
|
+
5. Complex schemas → properly typed
|
|
141
|
+
|
|
142
|
+
## Templates
|
|
143
|
+
Store templates in `templates/` directory:
|
|
144
|
+
- `templates/js-esm.hbs`
|
|
145
|
+
- `templates/ts.hbs`
|
|
146
|
+
- `templates/react.hbs`
|
|
147
|
+
- `templates/vue.hbs`
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Spec: Scan Command
|
|
2
|
+
|
|
3
|
+
## JTBD
|
|
4
|
+
As a developer, I want to scan any website and identify elements that could become WebMCP tools, so I can understand what annotations my site needs.
|
|
5
|
+
|
|
6
|
+
## Command
|
|
7
|
+
```bash
|
|
8
|
+
wmcp-annotate scan <url> [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--depth <n> How many pages deep to crawl (default: 1)
|
|
12
|
+
--output, -o Output file
|
|
13
|
+
--format, -f Output format: json, table, markdown
|
|
14
|
+
--verbose, -v Show detailed progress
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Behavior
|
|
18
|
+
|
|
19
|
+
### Input
|
|
20
|
+
- URL of website to scan
|
|
21
|
+
- Optional: crawl depth, output preferences
|
|
22
|
+
|
|
23
|
+
### Process
|
|
24
|
+
1. Launch headless browser (Playwright)
|
|
25
|
+
2. Navigate to URL
|
|
26
|
+
3. Wait for page load (network idle)
|
|
27
|
+
4. Identify actionable elements:
|
|
28
|
+
- Forms (with inputs, selects, textareas)
|
|
29
|
+
- Buttons (submit, action buttons)
|
|
30
|
+
- Links (navigation, actions)
|
|
31
|
+
- AJAX/fetch calls (intercepted)
|
|
32
|
+
5. For each element, extract:
|
|
33
|
+
- Type (form, button, link, api)
|
|
34
|
+
- Selector (CSS/XPath)
|
|
35
|
+
- Visible text/label
|
|
36
|
+
- Input fields (for forms)
|
|
37
|
+
- Current values
|
|
38
|
+
6. If depth > 1, follow links and repeat
|
|
39
|
+
|
|
40
|
+
### Output
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"url": "https://example.com",
|
|
44
|
+
"scannedAt": "2026-02-24T02:45:00Z",
|
|
45
|
+
"elements": [
|
|
46
|
+
{
|
|
47
|
+
"type": "form",
|
|
48
|
+
"id": "search-form",
|
|
49
|
+
"selector": "#search-form",
|
|
50
|
+
"label": "Search",
|
|
51
|
+
"inputs": [
|
|
52
|
+
{
|
|
53
|
+
"name": "query",
|
|
54
|
+
"type": "text",
|
|
55
|
+
"label": "Search query",
|
|
56
|
+
"required": true
|
|
57
|
+
}
|
|
58
|
+
],
|
|
59
|
+
"submitButton": {
|
|
60
|
+
"selector": "#search-form button[type=submit]",
|
|
61
|
+
"label": "Search"
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"type": "button",
|
|
66
|
+
"selector": ".add-to-cart",
|
|
67
|
+
"label": "Add to Cart",
|
|
68
|
+
"context": "product-page"
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"apiCalls": [
|
|
72
|
+
{
|
|
73
|
+
"method": "GET",
|
|
74
|
+
"url": "/api/products",
|
|
75
|
+
"params": ["category", "limit"]
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Tests
|
|
82
|
+
1. Scan a simple static site → finds forms and links
|
|
83
|
+
2. Scan a SPA (React) → finds dynamic elements
|
|
84
|
+
3. Scan with depth=2 → follows links
|
|
85
|
+
4. Scan site with API calls → intercepts fetch/XHR
|
|
86
|
+
5. Handle errors (404, timeout, blocked)
|
|
87
|
+
|
|
88
|
+
## Edge Cases
|
|
89
|
+
- Sites that block headless browsers → retry with stealth
|
|
90
|
+
- SPAs that need interaction to reveal elements → basic interaction
|
|
91
|
+
- Infinite scroll → limit to visible content
|
|
92
|
+
- Auth-required pages → accept cookies/headers option
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Spec: Suggest Command
|
|
2
|
+
|
|
3
|
+
## JTBD
|
|
4
|
+
As a developer, I want to get AI-powered suggestions for WebMCP tool definitions based on my site's elements, so I can quickly understand what tools to implement.
|
|
5
|
+
|
|
6
|
+
## Command
|
|
7
|
+
```bash
|
|
8
|
+
wmcp-annotate suggest <url> [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--scan-file, -s Use existing scan output instead of live scan
|
|
12
|
+
--output, -o Output file
|
|
13
|
+
--format, -f Output format: json, yaml
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Behavior
|
|
17
|
+
|
|
18
|
+
### Input
|
|
19
|
+
- URL to analyze (or existing scan file)
|
|
20
|
+
|
|
21
|
+
### Process
|
|
22
|
+
1. Run scan (or load scan file)
|
|
23
|
+
2. For each identified element:
|
|
24
|
+
- Analyze semantic meaning using AI
|
|
25
|
+
- Determine appropriate tool name (camelCase, descriptive)
|
|
26
|
+
- Generate inputSchema (JSON Schema)
|
|
27
|
+
- Write human-readable description
|
|
28
|
+
- Identify if read-only (no confirmation needed)
|
|
29
|
+
3. Group tools logically
|
|
30
|
+
4. Add metadata (version, author hints)
|
|
31
|
+
|
|
32
|
+
### Output
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"version": "1.0.0",
|
|
36
|
+
"tools": [
|
|
37
|
+
{
|
|
38
|
+
"name": "searchProducts",
|
|
39
|
+
"description": "Search for products by keyword, category, or filters",
|
|
40
|
+
"readOnly": true,
|
|
41
|
+
"inputSchema": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"query": {
|
|
45
|
+
"type": "string",
|
|
46
|
+
"description": "Search keywords"
|
|
47
|
+
},
|
|
48
|
+
"category": {
|
|
49
|
+
"type": "string",
|
|
50
|
+
"enum": ["electronics", "clothing", "home"],
|
|
51
|
+
"description": "Product category filter"
|
|
52
|
+
},
|
|
53
|
+
"maxPrice": {
|
|
54
|
+
"type": "number",
|
|
55
|
+
"description": "Maximum price filter"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"required": ["query"]
|
|
59
|
+
},
|
|
60
|
+
"sourceElement": {
|
|
61
|
+
"type": "form",
|
|
62
|
+
"selector": "#search-form"
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"name": "addToCart",
|
|
67
|
+
"description": "Add a product to the shopping cart",
|
|
68
|
+
"readOnly": false,
|
|
69
|
+
"inputSchema": {
|
|
70
|
+
"type": "object",
|
|
71
|
+
"properties": {
|
|
72
|
+
"productId": {
|
|
73
|
+
"type": "string",
|
|
74
|
+
"description": "Product identifier"
|
|
75
|
+
},
|
|
76
|
+
"quantity": {
|
|
77
|
+
"type": "integer",
|
|
78
|
+
"default": 1,
|
|
79
|
+
"minimum": 1
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"required": ["productId"]
|
|
83
|
+
},
|
|
84
|
+
"sourceElement": {
|
|
85
|
+
"type": "button",
|
|
86
|
+
"selector": ".add-to-cart"
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## AI Analysis Prompt
|
|
94
|
+
```
|
|
95
|
+
Given this website element:
|
|
96
|
+
- Type: {type}
|
|
97
|
+
- Label: {label}
|
|
98
|
+
- Inputs: {inputs}
|
|
99
|
+
- Context: {context}
|
|
100
|
+
|
|
101
|
+
Generate a WebMCP tool definition:
|
|
102
|
+
1. Tool name (camelCase, action-oriented verb)
|
|
103
|
+
2. Description (1-2 sentences, what it does)
|
|
104
|
+
3. Is it read-only? (search/view = yes, create/update/delete = no)
|
|
105
|
+
4. Input schema (JSON Schema with descriptions)
|
|
106
|
+
|
|
107
|
+
Follow WebMCP spec conventions.
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Tests
|
|
111
|
+
1. Form → generates tool with input schema matching form fields
|
|
112
|
+
2. Button → generates action tool
|
|
113
|
+
3. Search form → marks as readOnly: true
|
|
114
|
+
4. Checkout button → marks as readOnly: false
|
|
115
|
+
5. Complex form → handles nested fields
|
|
116
|
+
|
|
117
|
+
## Rate Limiting
|
|
118
|
+
- Free tier: 10 suggestions/day
|
|
119
|
+
- Pro tier: unlimited
|
|
120
|
+
- Cache results for same URL (1 hour TTL)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Spec: Validate Command
|
|
2
|
+
|
|
3
|
+
## JTBD
|
|
4
|
+
As a developer, I want to validate my existing WebMCP implementation against the W3C spec, so I can ensure compatibility with AI agents.
|
|
5
|
+
|
|
6
|
+
## Command
|
|
7
|
+
```bash
|
|
8
|
+
wmcp-annotate validate <url> [options]
|
|
9
|
+
|
|
10
|
+
Options:
|
|
11
|
+
--output, -o Output file
|
|
12
|
+
--format, -f Output format: json, table, markdown
|
|
13
|
+
--strict Fail on warnings (not just errors)
|
|
14
|
+
--ci Exit code 1 on any issues (for CI)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Behavior
|
|
18
|
+
|
|
19
|
+
### Input
|
|
20
|
+
- URL of site with WebMCP implementation
|
|
21
|
+
|
|
22
|
+
### Process
|
|
23
|
+
1. Launch browser with WebMCP flag enabled
|
|
24
|
+
2. Navigate to URL
|
|
25
|
+
3. Query `navigator.modelContext.tools` or equivalent
|
|
26
|
+
4. For each registered tool, validate:
|
|
27
|
+
- Name follows conventions (camelCase, no spaces)
|
|
28
|
+
- Description is present and meaningful
|
|
29
|
+
- inputSchema is valid JSON Schema
|
|
30
|
+
- Required fields are specified
|
|
31
|
+
- Types are correct
|
|
32
|
+
- Execute handler is async function
|
|
33
|
+
- ReadOnly hint is appropriate
|
|
34
|
+
5. Test tool execution (optional, with --test flag)
|
|
35
|
+
6. Generate report
|
|
36
|
+
|
|
37
|
+
### Output
|
|
38
|
+
```json
|
|
39
|
+
{
|
|
40
|
+
"url": "https://example.com",
|
|
41
|
+
"validatedAt": "2026-02-24T03:00:00Z",
|
|
42
|
+
"summary": {
|
|
43
|
+
"total": 5,
|
|
44
|
+
"valid": 4,
|
|
45
|
+
"warnings": 1,
|
|
46
|
+
"errors": 0
|
|
47
|
+
},
|
|
48
|
+
"tools": [
|
|
49
|
+
{
|
|
50
|
+
"name": "searchProducts",
|
|
51
|
+
"status": "valid",
|
|
52
|
+
"checks": {
|
|
53
|
+
"nameConvention": "pass",
|
|
54
|
+
"descriptionPresent": "pass",
|
|
55
|
+
"schemaValid": "pass",
|
|
56
|
+
"handlerAsync": "pass"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "checkout",
|
|
61
|
+
"status": "warning",
|
|
62
|
+
"checks": {
|
|
63
|
+
"nameConvention": "pass",
|
|
64
|
+
"descriptionPresent": "warning",
|
|
65
|
+
"schemaValid": "pass",
|
|
66
|
+
"handlerAsync": "pass"
|
|
67
|
+
},
|
|
68
|
+
"issues": [
|
|
69
|
+
{
|
|
70
|
+
"level": "warning",
|
|
71
|
+
"code": "DESCRIPTION_TOO_SHORT",
|
|
72
|
+
"message": "Description should be at least 20 characters",
|
|
73
|
+
"suggestion": "Add more detail about what this tool does"
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Validation Rules
|
|
82
|
+
|
|
83
|
+
| Rule | Level | Description |
|
|
84
|
+
|------|-------|-------------|
|
|
85
|
+
| NAME_FORMAT | error | Name must be camelCase, alphanumeric |
|
|
86
|
+
| NAME_LENGTH | warning | Name should be 3-50 characters |
|
|
87
|
+
| DESCRIPTION_MISSING | error | Description is required |
|
|
88
|
+
| DESCRIPTION_TOO_SHORT | warning | Description < 20 chars |
|
|
89
|
+
| SCHEMA_INVALID | error | inputSchema must be valid JSON Schema |
|
|
90
|
+
| SCHEMA_NO_DESCRIPTION | warning | Properties should have descriptions |
|
|
91
|
+
| HANDLER_NOT_ASYNC | error | Execute must be async function |
|
|
92
|
+
| HANDLER_MISSING | error | Execute handler is required |
|
|
93
|
+
| READONLY_HINT | warning | Consider marking read-only tools |
|
|
94
|
+
|
|
95
|
+
## Tests
|
|
96
|
+
1. Valid implementation → all pass
|
|
97
|
+
2. Missing description → error reported
|
|
98
|
+
3. Invalid schema → error reported
|
|
99
|
+
4. Sync handler → error reported
|
|
100
|
+
5. --ci flag → exits 1 on errors
|
|
101
|
+
6. --strict flag → exits 1 on warnings
|
|
102
|
+
|
|
103
|
+
## CI Integration Example
|
|
104
|
+
```yaml
|
|
105
|
+
# .github/workflows/webmcp.yml
|
|
106
|
+
- name: Validate WebMCP
|
|
107
|
+
run: npx wmcp-annotate validate ${{ env.DEPLOY_URL }} --ci
|
|
108
|
+
```
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { GenerateOptions, SuggestResult } from '../types.js';
|
|
2
|
+
import { generator } from '../lib/generator.js';
|
|
3
|
+
import { analyzer } from '../lib/analyzer.js';
|
|
4
|
+
import { scanner } from '../lib/scanner.js';
|
|
5
|
+
import { writeOutput, readInput } from '../lib/output.js';
|
|
6
|
+
import ora from 'ora';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
export async function generateCommand(url: string | undefined, options: GenerateOptions): Promise<void> {
|
|
10
|
+
const spinner = ora('Generating...').start();
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// Get suggestions (from file or live analysis)
|
|
14
|
+
let suggestions: SuggestResult;
|
|
15
|
+
if (options.suggestFile) {
|
|
16
|
+
spinner.text = 'Loading suggestions file...';
|
|
17
|
+
suggestions = await readInput<SuggestResult>(options.suggestFile);
|
|
18
|
+
} else if (url) {
|
|
19
|
+
spinner.text = `Scanning ${url}...`;
|
|
20
|
+
const scanResult = await scanner.scan(url, { depth: 1 });
|
|
21
|
+
|
|
22
|
+
spinner.text = 'Generating tool suggestions...';
|
|
23
|
+
suggestions = await analyzer.suggest(scanResult);
|
|
24
|
+
} else {
|
|
25
|
+
throw new Error('Either URL or --suggest-file is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
spinner.text = `Generating ${options.format.toUpperCase()} code...`;
|
|
29
|
+
const code = await generator.generate(suggestions, {
|
|
30
|
+
format: options.format as 'js' | 'ts' | 'react' | 'vue',
|
|
31
|
+
module: options.module as 'esm' | 'cjs',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
spinner.succeed(`Generated code for ${suggestions.tools.length} tools`);
|
|
35
|
+
|
|
36
|
+
if (options.output) {
|
|
37
|
+
const fs = await import('fs/promises');
|
|
38
|
+
await fs.writeFile(options.output, code, 'utf-8');
|
|
39
|
+
console.log(chalk.green(`\\nSaved to ${options.output}`));
|
|
40
|
+
} else {
|
|
41
|
+
console.log('\\n' + code);
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
spinner.fail('Generation failed');
|
|
45
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { ScanOptions, ScanResult } from '../types.js';
|
|
2
|
+
import { scanner } from '../lib/scanner.js';
|
|
3
|
+
import { writeOutput } from '../lib/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
export async function scanCommand(url: string, options: ScanOptions): Promise<void> {
|
|
8
|
+
const spinner = ora(`Scanning ${url}...`).start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const result = await scanner.scan(url, {
|
|
12
|
+
depth: parseInt(options.depth, 10),
|
|
13
|
+
verbose: options.verbose,
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
spinner.succeed(`Found ${result.elements.length} elements and ${result.apiCalls.length} API calls`);
|
|
17
|
+
|
|
18
|
+
await writeOutput(result, options);
|
|
19
|
+
|
|
20
|
+
if (!options.output) {
|
|
21
|
+
console.log(chalk.dim('\\nUse --output to save results to a file'));
|
|
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
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { SuggestOptions, SuggestResult, ScanResult } from '../types.js';
|
|
2
|
+
import { analyzer } from '../lib/analyzer.js';
|
|
3
|
+
import { scanner } from '../lib/scanner.js';
|
|
4
|
+
import { writeOutput, readInput } from '../lib/output.js';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
export async function suggestCommand(url: string | undefined, options: SuggestOptions): Promise<void> {
|
|
9
|
+
const spinner = ora('Analyzing...').start();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
// Get scan results (from file or live scan)
|
|
13
|
+
let scanResult: ScanResult;
|
|
14
|
+
if (options.scanFile) {
|
|
15
|
+
spinner.text = 'Loading scan file...';
|
|
16
|
+
scanResult = await readInput<ScanResult>(options.scanFile);
|
|
17
|
+
} else if (url) {
|
|
18
|
+
spinner.text = `Scanning ${url}...`;
|
|
19
|
+
scanResult = await scanner.scan(url, { depth: 1 });
|
|
20
|
+
} else {
|
|
21
|
+
throw new Error('Either URL or --scan-file is required');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
spinner.text = 'Generating tool suggestions with AI...';
|
|
25
|
+
const result = await analyzer.suggest(scanResult);
|
|
26
|
+
|
|
27
|
+
spinner.succeed(`Generated ${result.tools.length} tool suggestions`);
|
|
28
|
+
|
|
29
|
+
await writeOutput(result, options);
|
|
30
|
+
|
|
31
|
+
if (!options.output) {
|
|
32
|
+
console.log(chalk.dim('\\nUse --output to save results to a file'));
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
spinner.fail('Suggestion failed');
|
|
36
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ValidateOptions, ValidationResult } from '../types.js';
|
|
2
|
+
import { validator } from '../lib/validator.js';
|
|
3
|
+
import { writeOutput } from '../lib/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
export async function validateCommand(url: string, options: ValidateOptions): Promise<void> {
|
|
8
|
+
const spinner = ora(`Validating ${url}...`).start();
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const result = await validator.validate(url);
|
|
12
|
+
|
|
13
|
+
const { summary } = result;
|
|
14
|
+
|
|
15
|
+
if (summary.errors > 0) {
|
|
16
|
+
spinner.fail(`Validation failed: ${summary.errors} errors, ${summary.warnings} warnings`);
|
|
17
|
+
} else if (summary.warnings > 0) {
|
|
18
|
+
spinner.warn(`Validation passed with ${summary.warnings} warnings`);
|
|
19
|
+
} else {
|
|
20
|
+
spinner.succeed(`Validation passed: ${summary.total} tools validated`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
await writeOutput(result, options);
|
|
24
|
+
|
|
25
|
+
// Handle CI mode
|
|
26
|
+
if (options.ci) {
|
|
27
|
+
if (summary.errors > 0 || (options.strict && summary.warnings > 0)) {
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Print summary
|
|
33
|
+
console.log('\\n' + chalk.bold('Summary:'));
|
|
34
|
+
console.log(` Total tools: ${summary.total}`);
|
|
35
|
+
console.log(` Valid: ${chalk.green(summary.valid)}`);
|
|
36
|
+
console.log(` Warnings: ${chalk.yellow(summary.warnings)}`);
|
|
37
|
+
console.log(` Errors: ${chalk.red(summary.errors)}`);
|
|
38
|
+
|
|
39
|
+
} catch (error) {
|
|
40
|
+
spinner.fail('Validation failed');
|
|
41
|
+
console.error(chalk.red(error instanceof Error ? error.message : 'Unknown error'));
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import { scanCommand } from './commands/scan.js';
|
|
5
|
+
import { suggestCommand } from './commands/suggest.js';
|
|
6
|
+
import { generateCommand } from './commands/generate.js';
|
|
7
|
+
import { validateCommand } from './commands/validate.js';
|
|
8
|
+
|
|
9
|
+
const program = new Command();
|
|
10
|
+
|
|
11
|
+
program
|
|
12
|
+
.name('wmcp-annotate')
|
|
13
|
+
.description('Make any website AI-agent ready with WebMCP annotations')
|
|
14
|
+
.version('1.0.0');
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.command('scan <url>')
|
|
18
|
+
.description('Analyze a website for WebMCP opportunities')
|
|
19
|
+
.option('-d, --depth <number>', 'How many pages deep to crawl', '1')
|
|
20
|
+
.option('-o, --output <file>', 'Output file')
|
|
21
|
+
.option('-f, --format <format>', 'Output format: json, table, markdown', 'json')
|
|
22
|
+
.option('-v, --verbose', 'Show detailed progress')
|
|
23
|
+
.action(scanCommand);
|
|
24
|
+
|
|
25
|
+
program
|
|
26
|
+
.command('suggest [url]')
|
|
27
|
+
.description('Generate AI-powered WebMCP tool suggestions')
|
|
28
|
+
.option('-s, --scan-file <file>', 'Use existing scan output')
|
|
29
|
+
.option('-o, --output <file>', 'Output file')
|
|
30
|
+
.option('-f, --format <format>', 'Output format: json, yaml', 'json')
|
|
31
|
+
.action(suggestCommand);
|
|
32
|
+
|
|
33
|
+
program
|
|
34
|
+
.command('generate [url]')
|
|
35
|
+
.description('Generate WebMCP registration code')
|
|
36
|
+
.option('-s, --suggest-file <file>', 'Use existing suggestions')
|
|
37
|
+
.option('-o, --output <file>', 'Output file')
|
|
38
|
+
.option('-f, --format <format>', 'Output format: js, ts, react, vue', 'js')
|
|
39
|
+
.option('-m, --module <type>', 'Module format: esm, cjs', 'esm')
|
|
40
|
+
.action(generateCommand);
|
|
41
|
+
|
|
42
|
+
program
|
|
43
|
+
.command('validate <url>')
|
|
44
|
+
.description('Validate WebMCP implementation')
|
|
45
|
+
.option('-o, --output <file>', 'Output file')
|
|
46
|
+
.option('-f, --format <format>', 'Output format: json, table, markdown', 'json')
|
|
47
|
+
.option('--strict', 'Fail on warnings')
|
|
48
|
+
.option('--ci', 'Exit code 1 on any issues')
|
|
49
|
+
.action(validateCommand);
|
|
50
|
+
|
|
51
|
+
program.parse();
|