what-mcp 0.1.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.
- package/README.md +72 -0
- package/package.json +31 -0
- package/src/index.js +696 -0
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# what-mcp
|
|
2
|
+
|
|
3
|
+
[Model Context Protocol](https://modelcontextprotocol.io/) (MCP) server for [What Framework](https://whatfw.com). Gives AI assistants access to What Framework documentation, API references, and code examples.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install what-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
### With Claude Desktop
|
|
14
|
+
|
|
15
|
+
Add to your Claude Desktop config (`~/Library/Application Support/Claude/claude_desktop_config.json`):
|
|
16
|
+
|
|
17
|
+
```json
|
|
18
|
+
{
|
|
19
|
+
"mcpServers": {
|
|
20
|
+
"what-framework": {
|
|
21
|
+
"command": "npx",
|
|
22
|
+
"args": ["what-mcp"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Run directly
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npx what-mcp
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The server communicates over stdio using the MCP protocol.
|
|
35
|
+
|
|
36
|
+
## Available Tools
|
|
37
|
+
|
|
38
|
+
| Tool | Description |
|
|
39
|
+
|---|---|
|
|
40
|
+
| `what_overview` | Framework overview and key features |
|
|
41
|
+
| `what_signals` | Signals and reactive primitives (`signal`, `computed`, `effect`, `batch`) |
|
|
42
|
+
| `what_components` | Components, `h()` function, and mounting |
|
|
43
|
+
| `what_hooks` | React-compatible hooks (`useState`, `useEffect`, `useMemo`, etc.) |
|
|
44
|
+
| `what_islands` | Islands architecture and partial hydration |
|
|
45
|
+
| `what_routing` | File-based and programmatic routing |
|
|
46
|
+
| `what_forms` | Form utilities and validation rules |
|
|
47
|
+
| `what_data_fetching` | Data fetching with `useSWR` and `useQuery` |
|
|
48
|
+
| `what_animation` | Animation primitives (springs, tweens, gestures) |
|
|
49
|
+
| `what_accessibility` | Accessibility utilities (focus traps, ARIA helpers, screen reader) |
|
|
50
|
+
| `what_skeleton` | Skeleton loaders and loading states |
|
|
51
|
+
| `what_ssr` | Server-side rendering and static generation |
|
|
52
|
+
| `what_cli` | CLI commands and configuration |
|
|
53
|
+
| `what_search` | Search across all documentation topics |
|
|
54
|
+
|
|
55
|
+
## Example
|
|
56
|
+
|
|
57
|
+
When connected, an AI assistant can use these tools to answer questions about What Framework:
|
|
58
|
+
|
|
59
|
+
- "How do I create a signal?" -> calls `what_signals`
|
|
60
|
+
- "Show me how routing works" -> calls `what_routing`
|
|
61
|
+
- "How do I set up SSR?" -> calls `what_ssr`
|
|
62
|
+
- "Search for useForm" -> calls `what_search` with query "useForm"
|
|
63
|
+
|
|
64
|
+
## Links
|
|
65
|
+
|
|
66
|
+
- [Documentation](https://whatfw.com)
|
|
67
|
+
- [GitHub](https://github.com/zvndev/what-fw)
|
|
68
|
+
- [MCP Specification](https://modelcontextprotocol.io/)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "what-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP Server for What Framework documentation and assistance",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"what-mcp": "./src/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"mcp",
|
|
15
|
+
"what-framework",
|
|
16
|
+
"documentation"
|
|
17
|
+
],
|
|
18
|
+
"author": "",
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "https://github.com/zvndev/what-fw"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/zvndev/what-fw/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://whatfw.com"
|
|
31
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* What Framework MCP Server
|
|
5
|
+
*
|
|
6
|
+
* Provides documentation and assistance for the What Framework.
|
|
7
|
+
* Exposes tools for getting API reference, examples, and guidance.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
11
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
12
|
+
import {
|
|
13
|
+
CallToolRequestSchema,
|
|
14
|
+
ListToolsRequestSchema,
|
|
15
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
16
|
+
|
|
17
|
+
// Framework documentation content
|
|
18
|
+
const DOCS = {
|
|
19
|
+
overview: `
|
|
20
|
+
What Framework is a lightweight, signals-based web framework.
|
|
21
|
+
|
|
22
|
+
Key features:
|
|
23
|
+
- Signals: Fine-grained reactivity without virtual DOM diffing
|
|
24
|
+
- Islands: Zero JS by default, hydrate components on demand
|
|
25
|
+
- ~4kB gzipped: Tiny bundle size
|
|
26
|
+
- React-compatible hooks: useState, useEffect, useMemo backed by signals
|
|
27
|
+
- File-based routing: Drop files in pages/, get routes
|
|
28
|
+
- SSR/SSG/Hybrid: Choose rendering mode per page
|
|
29
|
+
- TypeScript: Full type definitions included
|
|
30
|
+
`,
|
|
31
|
+
|
|
32
|
+
signals: `
|
|
33
|
+
## Signals
|
|
34
|
+
|
|
35
|
+
Signals are reactive primitives that hold values and notify subscribers when changed.
|
|
36
|
+
|
|
37
|
+
\`\`\`js
|
|
38
|
+
import { signal, computed, effect, batch, untrack } from 'what';
|
|
39
|
+
|
|
40
|
+
// Create a signal
|
|
41
|
+
const count = signal(0);
|
|
42
|
+
|
|
43
|
+
// Read value
|
|
44
|
+
count(); // 0
|
|
45
|
+
|
|
46
|
+
// Write value
|
|
47
|
+
count.set(5); // set to 5
|
|
48
|
+
count.set(c => c + 1); // increment
|
|
49
|
+
|
|
50
|
+
// Read without tracking
|
|
51
|
+
count.peek();
|
|
52
|
+
|
|
53
|
+
// Subscribe to changes
|
|
54
|
+
const unsub = count.subscribe(value => console.log(value));
|
|
55
|
+
|
|
56
|
+
// Computed (derived) signals
|
|
57
|
+
const doubled = computed(() => count() * 2);
|
|
58
|
+
|
|
59
|
+
// Effects (side effects)
|
|
60
|
+
const dispose = effect(() => {
|
|
61
|
+
console.log('Count is:', count());
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Batch multiple updates
|
|
65
|
+
batch(() => {
|
|
66
|
+
a.set(1);
|
|
67
|
+
b.set(2);
|
|
68
|
+
// Effects run once at the end
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Read without subscribing in an effect
|
|
72
|
+
effect(() => {
|
|
73
|
+
const val = untrack(() => someSignal());
|
|
74
|
+
});
|
|
75
|
+
\`\`\`
|
|
76
|
+
`,
|
|
77
|
+
|
|
78
|
+
components: `
|
|
79
|
+
## Components
|
|
80
|
+
|
|
81
|
+
Components are functions that return VNodes.
|
|
82
|
+
|
|
83
|
+
\`\`\`js
|
|
84
|
+
import { h, mount, signal } from 'what';
|
|
85
|
+
|
|
86
|
+
// Simple component
|
|
87
|
+
function Greeting({ name }) {
|
|
88
|
+
return h('div', null, 'Hello, ', name);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Stateful component with signals
|
|
92
|
+
function Counter() {
|
|
93
|
+
const count = signal(0);
|
|
94
|
+
|
|
95
|
+
return h('div', null,
|
|
96
|
+
h('p', null, 'Count: ', () => count()),
|
|
97
|
+
h('button', { onClick: () => count.set(c => c + 1) }, '+'),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Mount to DOM
|
|
102
|
+
mount(h(Counter), '#app');
|
|
103
|
+
\`\`\`
|
|
104
|
+
|
|
105
|
+
### h() Function
|
|
106
|
+
|
|
107
|
+
\`\`\`js
|
|
108
|
+
h(tag, props, ...children)
|
|
109
|
+
h('div', { class: 'box' }, 'Hello')
|
|
110
|
+
h('input', { type: 'text', onInput: e => {} })
|
|
111
|
+
h(MyComponent, { name: 'World' })
|
|
112
|
+
\`\`\`
|
|
113
|
+
|
|
114
|
+
Props handling:
|
|
115
|
+
- \`class\` / \`className\` → el.className
|
|
116
|
+
- \`style\` (object or string) → el.style
|
|
117
|
+
- \`on*\` → event listeners (onClick → click)
|
|
118
|
+
- \`ref\` → ref.current = el
|
|
119
|
+
- \`key\` → list reconciliation
|
|
120
|
+
`,
|
|
121
|
+
|
|
122
|
+
hooks: `
|
|
123
|
+
## Hooks
|
|
124
|
+
|
|
125
|
+
React-compatible hooks backed by signals.
|
|
126
|
+
|
|
127
|
+
\`\`\`js
|
|
128
|
+
import { useState, useEffect, useMemo, useCallback, useRef, useReducer, createContext, useContext } from 'what';
|
|
129
|
+
|
|
130
|
+
// State
|
|
131
|
+
const [count, setCount] = useState(0);
|
|
132
|
+
setCount(5);
|
|
133
|
+
setCount(c => c + 1);
|
|
134
|
+
|
|
135
|
+
// Effect
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
const id = setInterval(tick, 1000);
|
|
138
|
+
return () => clearInterval(id);
|
|
139
|
+
}, [dependency]);
|
|
140
|
+
|
|
141
|
+
// Memo
|
|
142
|
+
const expensive = useMemo(() => compute(a, b), [a, b]);
|
|
143
|
+
|
|
144
|
+
// Callback
|
|
145
|
+
const handler = useCallback(e => doStuff(e), [dep]);
|
|
146
|
+
|
|
147
|
+
// Ref
|
|
148
|
+
const ref = useRef(null);
|
|
149
|
+
h('input', { ref }); // ref.current = <input>
|
|
150
|
+
|
|
151
|
+
// Reducer
|
|
152
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
153
|
+
|
|
154
|
+
// Context
|
|
155
|
+
const ThemeCtx = createContext('light');
|
|
156
|
+
h(ThemeCtx.Provider, { value: 'dark' }, children);
|
|
157
|
+
const theme = useContext(ThemeCtx);
|
|
158
|
+
\`\`\`
|
|
159
|
+
`,
|
|
160
|
+
|
|
161
|
+
islands: `
|
|
162
|
+
## Islands Architecture
|
|
163
|
+
|
|
164
|
+
Islands let you send zero JavaScript by default, hydrating only interactive parts.
|
|
165
|
+
|
|
166
|
+
\`\`\`js
|
|
167
|
+
import { island, Island } from 'what/server';
|
|
168
|
+
|
|
169
|
+
// Register an island with hydration strategy
|
|
170
|
+
island('cart', () => import('./islands/cart.js'), {
|
|
171
|
+
mode: 'action', // Hydrate on first interaction
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// Use in a page
|
|
175
|
+
function Page() {
|
|
176
|
+
return h('div', null,
|
|
177
|
+
h('nav', null, 'Static nav — no JS'),
|
|
178
|
+
h(Island, { name: 'cart' }), // Interactive island
|
|
179
|
+
h('footer', null, 'Static footer — no JS'),
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
### Hydration Modes
|
|
185
|
+
|
|
186
|
+
- \`'idle'\`: Hydrate when browser is idle
|
|
187
|
+
- \`'visible'\`: Hydrate when visible (IntersectionObserver)
|
|
188
|
+
- \`'action'\`: Hydrate on first click/focus
|
|
189
|
+
- \`'media'\`: Hydrate when media query matches
|
|
190
|
+
- \`'load'\`: Hydrate immediately
|
|
191
|
+
- \`'static'\`: Never hydrate (server only)
|
|
192
|
+
`,
|
|
193
|
+
|
|
194
|
+
routing: `
|
|
195
|
+
## Routing
|
|
196
|
+
|
|
197
|
+
File-based and programmatic routing.
|
|
198
|
+
|
|
199
|
+
\`\`\`js
|
|
200
|
+
import { Router, Link, navigate, route } from 'what/router';
|
|
201
|
+
|
|
202
|
+
// Declare routes
|
|
203
|
+
h(Router, {
|
|
204
|
+
routes: [
|
|
205
|
+
{ path: '/', component: Home },
|
|
206
|
+
{ path: '/about', component: About },
|
|
207
|
+
{ path: '/users/:id', component: User },
|
|
208
|
+
{ path: '/blog/*', component: BlogLayout },
|
|
209
|
+
],
|
|
210
|
+
fallback: h(NotFound),
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Navigation
|
|
214
|
+
h(Link, { href: '/about' }, 'About');
|
|
215
|
+
navigate('/dashboard');
|
|
216
|
+
navigate('/login', { replace: true });
|
|
217
|
+
|
|
218
|
+
// Reactive route state
|
|
219
|
+
route.path(); // current path
|
|
220
|
+
route.params(); // { id: '123' }
|
|
221
|
+
route.query(); // { page: '1' }
|
|
222
|
+
\`\`\`
|
|
223
|
+
|
|
224
|
+
### Nested Layouts
|
|
225
|
+
|
|
226
|
+
\`\`\`js
|
|
227
|
+
{
|
|
228
|
+
path: '/dashboard',
|
|
229
|
+
component: DashboardLayout,
|
|
230
|
+
children: [
|
|
231
|
+
{ path: '', component: DashboardHome },
|
|
232
|
+
{ path: 'settings', component: Settings },
|
|
233
|
+
],
|
|
234
|
+
}
|
|
235
|
+
\`\`\`
|
|
236
|
+
`,
|
|
237
|
+
|
|
238
|
+
forms: `
|
|
239
|
+
## Form Utilities
|
|
240
|
+
|
|
241
|
+
React Hook Form-like API.
|
|
242
|
+
|
|
243
|
+
\`\`\`js
|
|
244
|
+
import { useForm, rules, simpleResolver } from 'what';
|
|
245
|
+
|
|
246
|
+
function SignupForm() {
|
|
247
|
+
const { register, handleSubmit, formState } = useForm({
|
|
248
|
+
defaultValues: { email: '', password: '' },
|
|
249
|
+
resolver: simpleResolver({
|
|
250
|
+
email: [rules.required(), rules.email()],
|
|
251
|
+
password: [rules.required(), rules.minLength(8)],
|
|
252
|
+
}),
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return h('form', { onSubmit: handleSubmit(onSubmit) },
|
|
256
|
+
h('input', { ...register('email') }),
|
|
257
|
+
h('input', { ...register('password'), type: 'password' }),
|
|
258
|
+
h('button', { type: 'submit' }, 'Submit'),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
\`\`\`
|
|
262
|
+
|
|
263
|
+
### Validation Rules
|
|
264
|
+
|
|
265
|
+
\`\`\`js
|
|
266
|
+
rules.required(message?)
|
|
267
|
+
rules.minLength(min, message?)
|
|
268
|
+
rules.maxLength(max, message?)
|
|
269
|
+
rules.min(min, message?)
|
|
270
|
+
rules.max(max, message?)
|
|
271
|
+
rules.pattern(regex, message?)
|
|
272
|
+
rules.email(message?)
|
|
273
|
+
rules.url(message?)
|
|
274
|
+
rules.match(field, message?)
|
|
275
|
+
rules.custom(validator)
|
|
276
|
+
\`\`\`
|
|
277
|
+
`,
|
|
278
|
+
|
|
279
|
+
dataFetching: `
|
|
280
|
+
## Data Fetching
|
|
281
|
+
|
|
282
|
+
SWR-like hooks for data fetching.
|
|
283
|
+
|
|
284
|
+
\`\`\`js
|
|
285
|
+
import { useSWR, useQuery, useInfiniteQuery } from 'what';
|
|
286
|
+
|
|
287
|
+
// useSWR
|
|
288
|
+
const { data, error, isLoading, mutate, revalidate } = useSWR(
|
|
289
|
+
'user-data',
|
|
290
|
+
(key) => fetch('/api/user').then(r => r.json()),
|
|
291
|
+
{ revalidateOnFocus: true }
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
// useQuery (TanStack Query-like)
|
|
295
|
+
const { data, error, isLoading, refetch } = useQuery({
|
|
296
|
+
queryKey: ['todos', userId],
|
|
297
|
+
queryFn: ({ queryKey }) => fetchTodos(queryKey[1]),
|
|
298
|
+
staleTime: 5000,
|
|
299
|
+
cacheTime: 5 * 60 * 1000,
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
// useInfiniteQuery
|
|
303
|
+
const { data, fetchNextPage, hasNextPage } = useInfiniteQuery({
|
|
304
|
+
queryKey: ['posts'],
|
|
305
|
+
queryFn: ({ pageParam }) => fetchPosts(pageParam),
|
|
306
|
+
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
307
|
+
initialPageParam: 0,
|
|
308
|
+
});
|
|
309
|
+
\`\`\`
|
|
310
|
+
|
|
311
|
+
### Cache Management
|
|
312
|
+
|
|
313
|
+
\`\`\`js
|
|
314
|
+
import { invalidateQueries, prefetchQuery, setQueryData, clearCache } from 'what';
|
|
315
|
+
|
|
316
|
+
invalidateQueries('user-data');
|
|
317
|
+
prefetchQuery('user', fetcher);
|
|
318
|
+
setQueryData('user', { name: 'John' });
|
|
319
|
+
clearCache();
|
|
320
|
+
\`\`\`
|
|
321
|
+
`,
|
|
322
|
+
|
|
323
|
+
animation: `
|
|
324
|
+
## Animation
|
|
325
|
+
|
|
326
|
+
Physics-based springs and tweens.
|
|
327
|
+
|
|
328
|
+
\`\`\`js
|
|
329
|
+
import { spring, tween, easings, useGesture } from 'what';
|
|
330
|
+
|
|
331
|
+
// Spring animation
|
|
332
|
+
const x = spring(0, { stiffness: 100, damping: 10 });
|
|
333
|
+
x.set(100); // Animate to 100
|
|
334
|
+
x.current(); // Current value
|
|
335
|
+
x.snap(50); // Immediately set to 50
|
|
336
|
+
|
|
337
|
+
// Tween animation
|
|
338
|
+
const t = tween(0, 100, {
|
|
339
|
+
duration: 300,
|
|
340
|
+
easing: easings.easeOutQuad,
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
// Gesture handling
|
|
344
|
+
useGesture(ref, {
|
|
345
|
+
onDrag: ({ deltaX, deltaY, velocity }) => {},
|
|
346
|
+
onSwipe: ({ direction }) => {},
|
|
347
|
+
onTap: ({ x, y }) => {},
|
|
348
|
+
onPinch: ({ scale }) => {},
|
|
349
|
+
});
|
|
350
|
+
\`\`\`
|
|
351
|
+
`,
|
|
352
|
+
|
|
353
|
+
accessibility: `
|
|
354
|
+
## Accessibility
|
|
355
|
+
|
|
356
|
+
Built-in accessibility utilities.
|
|
357
|
+
|
|
358
|
+
\`\`\`js
|
|
359
|
+
import {
|
|
360
|
+
useFocusTrap, FocusTrap,
|
|
361
|
+
announce, announceAssertive,
|
|
362
|
+
SkipLink,
|
|
363
|
+
useAriaExpanded,
|
|
364
|
+
useRovingTabIndex,
|
|
365
|
+
VisuallyHidden,
|
|
366
|
+
Keys, onKey,
|
|
367
|
+
} from 'what';
|
|
368
|
+
|
|
369
|
+
// Focus trap for modals
|
|
370
|
+
const trap = useFocusTrap(modalRef);
|
|
371
|
+
trap.activate();
|
|
372
|
+
trap.deactivate();
|
|
373
|
+
|
|
374
|
+
// Or as component
|
|
375
|
+
h(FocusTrap, { active: isOpen }, h(Modal));
|
|
376
|
+
|
|
377
|
+
// Screen reader announcements
|
|
378
|
+
announce('Item added to cart');
|
|
379
|
+
announceAssertive('Error: Form invalid');
|
|
380
|
+
|
|
381
|
+
// Skip navigation
|
|
382
|
+
h(SkipLink, { href: '#main' }, 'Skip to content');
|
|
383
|
+
|
|
384
|
+
// ARIA helpers
|
|
385
|
+
const { expanded, buttonProps, panelProps } = useAriaExpanded(false);
|
|
386
|
+
|
|
387
|
+
// Keyboard navigation
|
|
388
|
+
const { getItemProps } = useRovingTabIndex(items.length);
|
|
389
|
+
|
|
390
|
+
// Key handling
|
|
391
|
+
h('input', { onKeyDown: onKey(Keys.Enter, submit) });
|
|
392
|
+
\`\`\`
|
|
393
|
+
`,
|
|
394
|
+
|
|
395
|
+
skeleton: `
|
|
396
|
+
## Skeleton Loaders
|
|
397
|
+
|
|
398
|
+
Loading state components.
|
|
399
|
+
|
|
400
|
+
\`\`\`js
|
|
401
|
+
import { Skeleton, SkeletonText, SkeletonCard, Spinner, LoadingDots } from 'what';
|
|
402
|
+
|
|
403
|
+
// Basic skeleton
|
|
404
|
+
h(Skeleton, { width: 200, height: 20 })
|
|
405
|
+
|
|
406
|
+
// Circle avatar
|
|
407
|
+
h(Skeleton, { width: 50, height: 50, circle: true })
|
|
408
|
+
|
|
409
|
+
// Text lines
|
|
410
|
+
h(SkeletonText, { lines: 3 })
|
|
411
|
+
|
|
412
|
+
// Card placeholder
|
|
413
|
+
h(SkeletonCard, { imageHeight: 200 })
|
|
414
|
+
|
|
415
|
+
// Spinner
|
|
416
|
+
h(Spinner, { size: 24 })
|
|
417
|
+
|
|
418
|
+
// Loading dots
|
|
419
|
+
h(LoadingDots, { size: 8 })
|
|
420
|
+
\`\`\`
|
|
421
|
+
|
|
422
|
+
Variants: 'shimmer' (default), 'pulse', 'wave'
|
|
423
|
+
`,
|
|
424
|
+
|
|
425
|
+
ssr: `
|
|
426
|
+
## Server-Side Rendering
|
|
427
|
+
|
|
428
|
+
SSR, SSG, and hybrid rendering.
|
|
429
|
+
|
|
430
|
+
\`\`\`js
|
|
431
|
+
import { renderToString, renderToStream, definePage, server } from 'what/server';
|
|
432
|
+
|
|
433
|
+
// Render to string
|
|
434
|
+
const html = await renderToString(h(App));
|
|
435
|
+
|
|
436
|
+
// Stream rendering
|
|
437
|
+
for await (const chunk of renderToStream(h(App))) {
|
|
438
|
+
response.write(chunk);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
// Per-page config
|
|
442
|
+
export const page = definePage({
|
|
443
|
+
mode: 'static', // 'static' | 'server' | 'client' | 'hybrid'
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// Server-only component
|
|
447
|
+
const Header = server(({ title }) => h('header', null, title));
|
|
448
|
+
\`\`\`
|
|
449
|
+
`,
|
|
450
|
+
|
|
451
|
+
cli: `
|
|
452
|
+
## CLI Commands
|
|
453
|
+
|
|
454
|
+
\`\`\`bash
|
|
455
|
+
what dev # Dev server with HMR
|
|
456
|
+
what build # Production build
|
|
457
|
+
what preview # Preview production build
|
|
458
|
+
what generate # Static site generation
|
|
459
|
+
\`\`\`
|
|
460
|
+
|
|
461
|
+
## Configuration
|
|
462
|
+
|
|
463
|
+
\`\`\`js
|
|
464
|
+
// what.config.js
|
|
465
|
+
export default {
|
|
466
|
+
mode: 'hybrid',
|
|
467
|
+
pagesDir: 'src/pages',
|
|
468
|
+
outDir: 'dist',
|
|
469
|
+
islands: true,
|
|
470
|
+
port: 3000,
|
|
471
|
+
};
|
|
472
|
+
\`\`\`
|
|
473
|
+
`,
|
|
474
|
+
};
|
|
475
|
+
|
|
476
|
+
// Create server
|
|
477
|
+
const server = new Server(
|
|
478
|
+
{
|
|
479
|
+
name: 'what-framework',
|
|
480
|
+
version: '0.1.0',
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
capabilities: {
|
|
484
|
+
tools: {},
|
|
485
|
+
},
|
|
486
|
+
}
|
|
487
|
+
);
|
|
488
|
+
|
|
489
|
+
// List available tools
|
|
490
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
491
|
+
return {
|
|
492
|
+
tools: [
|
|
493
|
+
{
|
|
494
|
+
name: 'what_overview',
|
|
495
|
+
description: 'Get an overview of What Framework',
|
|
496
|
+
inputSchema: {
|
|
497
|
+
type: 'object',
|
|
498
|
+
properties: {},
|
|
499
|
+
required: [],
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
name: 'what_signals',
|
|
504
|
+
description: 'Learn about signals and reactive primitives in What Framework',
|
|
505
|
+
inputSchema: {
|
|
506
|
+
type: 'object',
|
|
507
|
+
properties: {},
|
|
508
|
+
required: [],
|
|
509
|
+
},
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
name: 'what_components',
|
|
513
|
+
description: 'Learn about creating components and the h() function',
|
|
514
|
+
inputSchema: {
|
|
515
|
+
type: 'object',
|
|
516
|
+
properties: {},
|
|
517
|
+
required: [],
|
|
518
|
+
},
|
|
519
|
+
},
|
|
520
|
+
{
|
|
521
|
+
name: 'what_hooks',
|
|
522
|
+
description: 'Learn about React-compatible hooks (useState, useEffect, etc.)',
|
|
523
|
+
inputSchema: {
|
|
524
|
+
type: 'object',
|
|
525
|
+
properties: {},
|
|
526
|
+
required: [],
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
{
|
|
530
|
+
name: 'what_islands',
|
|
531
|
+
description: 'Learn about islands architecture and partial hydration',
|
|
532
|
+
inputSchema: {
|
|
533
|
+
type: 'object',
|
|
534
|
+
properties: {},
|
|
535
|
+
required: [],
|
|
536
|
+
},
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: 'what_routing',
|
|
540
|
+
description: 'Learn about file-based and programmatic routing',
|
|
541
|
+
inputSchema: {
|
|
542
|
+
type: 'object',
|
|
543
|
+
properties: {},
|
|
544
|
+
required: [],
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
name: 'what_forms',
|
|
549
|
+
description: 'Learn about form utilities and validation',
|
|
550
|
+
inputSchema: {
|
|
551
|
+
type: 'object',
|
|
552
|
+
properties: {},
|
|
553
|
+
required: [],
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
{
|
|
557
|
+
name: 'what_data_fetching',
|
|
558
|
+
description: 'Learn about data fetching with useSWR and useQuery',
|
|
559
|
+
inputSchema: {
|
|
560
|
+
type: 'object',
|
|
561
|
+
properties: {},
|
|
562
|
+
required: [],
|
|
563
|
+
},
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
name: 'what_animation',
|
|
567
|
+
description: 'Learn about animation primitives (spring, tween, gestures)',
|
|
568
|
+
inputSchema: {
|
|
569
|
+
type: 'object',
|
|
570
|
+
properties: {},
|
|
571
|
+
required: [],
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
name: 'what_accessibility',
|
|
576
|
+
description: 'Learn about accessibility utilities',
|
|
577
|
+
inputSchema: {
|
|
578
|
+
type: 'object',
|
|
579
|
+
properties: {},
|
|
580
|
+
required: [],
|
|
581
|
+
},
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
name: 'what_skeleton',
|
|
585
|
+
description: 'Learn about skeleton loaders and loading states',
|
|
586
|
+
inputSchema: {
|
|
587
|
+
type: 'object',
|
|
588
|
+
properties: {},
|
|
589
|
+
required: [],
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
name: 'what_ssr',
|
|
594
|
+
description: 'Learn about server-side rendering and static generation',
|
|
595
|
+
inputSchema: {
|
|
596
|
+
type: 'object',
|
|
597
|
+
properties: {},
|
|
598
|
+
required: [],
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
{
|
|
602
|
+
name: 'what_cli',
|
|
603
|
+
description: 'Learn about CLI commands and configuration',
|
|
604
|
+
inputSchema: {
|
|
605
|
+
type: 'object',
|
|
606
|
+
properties: {},
|
|
607
|
+
required: [],
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: 'what_search',
|
|
612
|
+
description: 'Search across all What Framework documentation',
|
|
613
|
+
inputSchema: {
|
|
614
|
+
type: 'object',
|
|
615
|
+
properties: {
|
|
616
|
+
query: {
|
|
617
|
+
type: 'string',
|
|
618
|
+
description: 'Search query',
|
|
619
|
+
},
|
|
620
|
+
},
|
|
621
|
+
required: ['query'],
|
|
622
|
+
},
|
|
623
|
+
},
|
|
624
|
+
],
|
|
625
|
+
};
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
// Handle tool calls
|
|
629
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
630
|
+
const { name, arguments: args } = request.params;
|
|
631
|
+
|
|
632
|
+
switch (name) {
|
|
633
|
+
case 'what_overview':
|
|
634
|
+
return { content: [{ type: 'text', text: DOCS.overview }] };
|
|
635
|
+
case 'what_signals':
|
|
636
|
+
return { content: [{ type: 'text', text: DOCS.signals }] };
|
|
637
|
+
case 'what_components':
|
|
638
|
+
return { content: [{ type: 'text', text: DOCS.components }] };
|
|
639
|
+
case 'what_hooks':
|
|
640
|
+
return { content: [{ type: 'text', text: DOCS.hooks }] };
|
|
641
|
+
case 'what_islands':
|
|
642
|
+
return { content: [{ type: 'text', text: DOCS.islands }] };
|
|
643
|
+
case 'what_routing':
|
|
644
|
+
return { content: [{ type: 'text', text: DOCS.routing }] };
|
|
645
|
+
case 'what_forms':
|
|
646
|
+
return { content: [{ type: 'text', text: DOCS.forms }] };
|
|
647
|
+
case 'what_data_fetching':
|
|
648
|
+
return { content: [{ type: 'text', text: DOCS.dataFetching }] };
|
|
649
|
+
case 'what_animation':
|
|
650
|
+
return { content: [{ type: 'text', text: DOCS.animation }] };
|
|
651
|
+
case 'what_accessibility':
|
|
652
|
+
return { content: [{ type: 'text', text: DOCS.accessibility }] };
|
|
653
|
+
case 'what_skeleton':
|
|
654
|
+
return { content: [{ type: 'text', text: DOCS.skeleton }] };
|
|
655
|
+
case 'what_ssr':
|
|
656
|
+
return { content: [{ type: 'text', text: DOCS.ssr }] };
|
|
657
|
+
case 'what_cli':
|
|
658
|
+
return { content: [{ type: 'text', text: DOCS.cli }] };
|
|
659
|
+
case 'what_search': {
|
|
660
|
+
const query = args?.query?.toLowerCase() || '';
|
|
661
|
+
const results = [];
|
|
662
|
+
|
|
663
|
+
for (const [topic, content] of Object.entries(DOCS)) {
|
|
664
|
+
if (content.toLowerCase().includes(query)) {
|
|
665
|
+
results.push({
|
|
666
|
+
topic,
|
|
667
|
+
excerpt: content.substring(0, 200) + '...',
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
if (results.length === 0) {
|
|
673
|
+
return {
|
|
674
|
+
content: [{ type: 'text', text: `No results found for "${query}"` }],
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
const text = results
|
|
679
|
+
.map((r) => `## ${r.topic}\n${r.excerpt}`)
|
|
680
|
+
.join('\n\n---\n\n');
|
|
681
|
+
|
|
682
|
+
return { content: [{ type: 'text', text }] };
|
|
683
|
+
}
|
|
684
|
+
default:
|
|
685
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
// Start server
|
|
690
|
+
async function main() {
|
|
691
|
+
const transport = new StdioServerTransport();
|
|
692
|
+
await server.connect(transport);
|
|
693
|
+
console.error('What Framework MCP Server running on stdio');
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
main().catch(console.error);
|