what-devtools-mcp 0.6.0 → 0.7.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/package.json +2 -2
- package/src/bridge.js +66 -2
- package/src/client-commands.js +604 -5
- package/src/client.js +53 -12
- package/src/index.js +450 -0
- package/src/tools-agent.js +183 -2
- package/src/tools-extended.js +408 -10
- package/src/tools.js +103 -14
- package/src/vite-plugin.js +32 -2
package/src/client.js
CHANGED
|
@@ -21,7 +21,7 @@ function logGrouped(badge, badgeStyle, title, data) {
|
|
|
21
21
|
console.groupEnd();
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
24
|
+
export function connectDevToolsMCP({ port = 9229, token = '' } = {}) {
|
|
25
25
|
// Never connect in production
|
|
26
26
|
if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
|
|
27
27
|
return { disconnect() {}, isConnected: false, eventCount: 0 };
|
|
@@ -42,19 +42,46 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
|
42
42
|
let hasLoggedDisconnect = false;
|
|
43
43
|
let reconnectAttempts = 0;
|
|
44
44
|
let unsubscribeFn = null;
|
|
45
|
+
let flushEventBatch = null; // Hoisted — set during subscription, called by set-signal
|
|
46
|
+
let discoveredToken = token;
|
|
47
|
+
let discoveredPort = port;
|
|
45
48
|
|
|
46
49
|
// Startup banner
|
|
47
50
|
console.log(
|
|
48
|
-
'%c⚡ What DevTools MCP %c Client v0.
|
|
51
|
+
'%c⚡ What DevTools MCP %c Client v0.2.0',
|
|
49
52
|
'background:linear-gradient(135deg,#6366f1,#a855f7);color:#fff;padding:4px 10px;border-radius:4px;font-weight:bold;font-size:13px',
|
|
50
53
|
'color:#a855f7;font-weight:bold'
|
|
51
54
|
);
|
|
55
|
+
|
|
56
|
+
// --- Token Auto-Discovery ---
|
|
57
|
+
// If no token is provided, try to discover it from the bridge's HTTP endpoint.
|
|
58
|
+
// The bridge serves GET http://localhost:{port+1}/__what_mcp_token
|
|
59
|
+
async function discoverToken() {
|
|
60
|
+
if (discoveredToken) return true; // Already have a token
|
|
61
|
+
|
|
62
|
+
const discoveryPort = port + 1;
|
|
63
|
+
try {
|
|
64
|
+
const res = await fetch(`http://localhost:${discoveryPort}/__what_mcp_token`);
|
|
65
|
+
if (res.ok) {
|
|
66
|
+
const data = await res.json();
|
|
67
|
+
discoveredToken = data.token;
|
|
68
|
+
discoveredPort = data.wsPort || port;
|
|
69
|
+
log('MCP', BADGE, `Token discovered automatically from bridge`);
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
} catch {
|
|
73
|
+
// Discovery endpoint not available — bridge may not be running yet
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
52
78
|
log('MCP', BADGE, `Connecting to bridge on ws://localhost:${port}`);
|
|
53
79
|
|
|
54
80
|
function connect() {
|
|
55
81
|
reconnectAttempts++;
|
|
56
82
|
try {
|
|
57
|
-
|
|
83
|
+
const tokenParam = discoveredToken ? `?token=${encodeURIComponent(discoveredToken)}` : '';
|
|
84
|
+
ws = new WebSocket(`ws://localhost:${discoveredPort}${tokenParam}`);
|
|
58
85
|
} catch {
|
|
59
86
|
if (reconnectAttempts <= 1) {
|
|
60
87
|
log('MCP', BADGE_WARN, 'Bridge not available — retrying silently in background');
|
|
@@ -91,18 +118,18 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
|
91
118
|
let eventBatch = [];
|
|
92
119
|
let batchTimer = null;
|
|
93
120
|
|
|
94
|
-
|
|
121
|
+
// Hoisted so set-signal can call it to flush after programmatic writes
|
|
122
|
+
flushEventBatch = function() {
|
|
95
123
|
if (eventBatch.length === 0) return;
|
|
96
124
|
if (eventBatch.length === 1) {
|
|
97
|
-
// Single event — send normally for compatibility
|
|
98
125
|
const item = eventBatch[0];
|
|
99
126
|
send({ type: 'event', event: item.event, data: item.data });
|
|
100
127
|
} else {
|
|
101
128
|
send({ type: 'events', batch: eventBatch });
|
|
102
129
|
}
|
|
103
130
|
eventBatch = [];
|
|
104
|
-
batchTimer = null;
|
|
105
|
-
}
|
|
131
|
+
if (batchTimer) { clearTimeout(batchTimer); batchTimer = null; }
|
|
132
|
+
};
|
|
106
133
|
|
|
107
134
|
unsubscribeFn = devtools.subscribe((event, data) => {
|
|
108
135
|
eventCount++;
|
|
@@ -187,6 +214,8 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
|
187
214
|
if (entry) {
|
|
188
215
|
const prev = entry.ref.peek();
|
|
189
216
|
entry.ref(value);
|
|
217
|
+
// Flush event batch immediately so what_watch captures programmatic writes
|
|
218
|
+
if (flushEventBatch) setTimeout(flushEventBatch, 0);
|
|
190
219
|
result = { previous: devtools.safeSerialize(prev), current: devtools.safeSerialize(value) };
|
|
191
220
|
log('AI →', BADGE_CMD, `${label} — signal #${signalId} "${entry.name}": ${JSON.stringify(prev)} → ${JSON.stringify(value)}`);
|
|
192
221
|
} else {
|
|
@@ -221,9 +250,17 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
|
221
250
|
// Try extended command handlers
|
|
222
251
|
let extResult = null;
|
|
223
252
|
try {
|
|
224
|
-
const { handleExtendedCommand } = await import('./client-commands.js');
|
|
225
|
-
|
|
226
|
-
|
|
253
|
+
const { handleExtendedCommand, initEventTracking } = await import('./client-commands.js');
|
|
254
|
+
// Auto-initialize event tracking on first extended command so
|
|
255
|
+
// what_signal_trace always has write history (not just after what_watch)
|
|
256
|
+
initEventTracking(devtools);
|
|
257
|
+
extResult = await handleExtendedCommand(command, args, devtools);
|
|
258
|
+
} catch (importErr) {
|
|
259
|
+
// Don't silently swallow — report the real error so it's not
|
|
260
|
+
// misdiagnosed as "Unknown command"
|
|
261
|
+
extResult = { error: `Command handler failed: ${importErr.message}` };
|
|
262
|
+
log('AI →', BADGE_WARN, `Extended command "${command}" threw:`, importErr);
|
|
263
|
+
}
|
|
227
264
|
|
|
228
265
|
if (extResult !== null) {
|
|
229
266
|
result = extResult;
|
|
@@ -248,9 +285,12 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
|
248
285
|
hasLoggedDisconnect = true;
|
|
249
286
|
}
|
|
250
287
|
const delay = reconnectAttempts >= 5 ? MAX_RECONNECT_DELAY : reconnectDelay;
|
|
251
|
-
reconnectTimer = setTimeout(() => {
|
|
288
|
+
reconnectTimer = setTimeout(async () => {
|
|
252
289
|
reconnectTimer = null;
|
|
253
290
|
reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
|
|
291
|
+
// Try to discover token before each reconnect attempt
|
|
292
|
+
// (bridge may have started since last attempt)
|
|
293
|
+
await discoverToken();
|
|
254
294
|
connect();
|
|
255
295
|
}, delay);
|
|
256
296
|
}
|
|
@@ -273,7 +313,8 @@ export function connectDevToolsMCP({ port = 9229 } = {}) {
|
|
|
273
313
|
log('MCP', BADGE, 'Disconnected');
|
|
274
314
|
}
|
|
275
315
|
|
|
276
|
-
connect
|
|
316
|
+
// Initial connect — try token discovery first, then connect
|
|
317
|
+
discoverToken().then(() => connect());
|
|
277
318
|
|
|
278
319
|
return {
|
|
279
320
|
disconnect,
|
package/src/index.js
CHANGED
|
@@ -138,6 +138,8 @@ server.resource(
|
|
|
138
138
|
mimeType: 'text/markdown',
|
|
139
139
|
text: `# What Framework API Reference
|
|
140
140
|
|
|
141
|
+
> **Signal style note:** WhatFW signals support both \`sig(newValue)\` and \`sig.set(newValue)\`. The idiomatic pattern is the function-call style: \`sig(newValue)\` to write, \`sig()\` to read.
|
|
142
|
+
|
|
141
143
|
## Reactive Primitives
|
|
142
144
|
- \`signal(initial, debugName?)\` — create reactive value. Read: \`sig()\`. Write: \`sig(newVal)\` or \`sig(prev => next)\`.
|
|
143
145
|
- \`computed(fn)\` — derived value (lazy, only recomputes when deps change AND it's read)
|
|
@@ -617,6 +619,391 @@ function UserProfile({ userId }) {
|
|
|
617
619
|
})
|
|
618
620
|
);
|
|
619
621
|
|
|
622
|
+
// --- Routing Resource (ported from mcp-server) ---
|
|
623
|
+
server.resource(
|
|
624
|
+
'routing-guide',
|
|
625
|
+
'what://docs/routing',
|
|
626
|
+
{ description: 'File-based and programmatic routing in What Framework: routes, params, nested layouts, navigation' },
|
|
627
|
+
async () => ({
|
|
628
|
+
contents: [{
|
|
629
|
+
uri: 'what://docs/routing',
|
|
630
|
+
mimeType: 'text/markdown',
|
|
631
|
+
text: `# What Framework Routing
|
|
632
|
+
|
|
633
|
+
## Declaring Routes
|
|
634
|
+
|
|
635
|
+
\`\`\`js
|
|
636
|
+
import { Router, Link, navigate, route } from 'what-framework/router';
|
|
637
|
+
|
|
638
|
+
h(Router, {
|
|
639
|
+
routes: [
|
|
640
|
+
{ path: '/', component: Home },
|
|
641
|
+
{ path: '/about', component: About },
|
|
642
|
+
{ path: '/users/:id', component: User },
|
|
643
|
+
{ path: '/blog/*', component: BlogLayout },
|
|
644
|
+
],
|
|
645
|
+
fallback: h(NotFound),
|
|
646
|
+
});
|
|
647
|
+
\`\`\`
|
|
648
|
+
|
|
649
|
+
## Navigation
|
|
650
|
+
|
|
651
|
+
\`\`\`js
|
|
652
|
+
// Declarative link
|
|
653
|
+
h(Link, { href: '/about' }, 'About');
|
|
654
|
+
|
|
655
|
+
// Programmatic navigation
|
|
656
|
+
navigate('/dashboard');
|
|
657
|
+
navigate('/login', { replace: true });
|
|
658
|
+
\`\`\`
|
|
659
|
+
|
|
660
|
+
## Reactive Route State
|
|
661
|
+
|
|
662
|
+
\`\`\`js
|
|
663
|
+
route.path(); // current path (signal — call to read)
|
|
664
|
+
route.params(); // { id: '123' }
|
|
665
|
+
route.query(); // { page: '1' }
|
|
666
|
+
\`\`\`
|
|
667
|
+
|
|
668
|
+
## Nested Layouts
|
|
669
|
+
|
|
670
|
+
\`\`\`js
|
|
671
|
+
{
|
|
672
|
+
path: '/dashboard',
|
|
673
|
+
component: DashboardLayout,
|
|
674
|
+
children: [
|
|
675
|
+
{ path: '', component: DashboardHome },
|
|
676
|
+
{ path: 'settings', component: Settings },
|
|
677
|
+
],
|
|
678
|
+
}
|
|
679
|
+
\`\`\`
|
|
680
|
+
|
|
681
|
+
## File-Based Routing
|
|
682
|
+
|
|
683
|
+
Drop files in \`src/pages/\` and routes are generated automatically:
|
|
684
|
+
|
|
685
|
+
| File | Route |
|
|
686
|
+
|------|-------|
|
|
687
|
+
| \`src/pages/index.jsx\` | \`/\` |
|
|
688
|
+
| \`src/pages/about.jsx\` | \`/about\` |
|
|
689
|
+
| \`src/pages/users/[id].jsx\` | \`/users/:id\` |
|
|
690
|
+
| \`src/pages/blog/[...slug].jsx\` | \`/blog/*\` |
|
|
691
|
+
|
|
692
|
+
## Route Guards
|
|
693
|
+
|
|
694
|
+
\`\`\`js
|
|
695
|
+
{
|
|
696
|
+
path: '/admin',
|
|
697
|
+
component: AdminPanel,
|
|
698
|
+
beforeEnter: (to, from) => {
|
|
699
|
+
if (!isAuthenticated()) return '/login';
|
|
700
|
+
},
|
|
701
|
+
}
|
|
702
|
+
\`\`\`
|
|
703
|
+
`,
|
|
704
|
+
}],
|
|
705
|
+
})
|
|
706
|
+
);
|
|
707
|
+
|
|
708
|
+
// --- SSR/SSG Resource (ported from mcp-server) ---
|
|
709
|
+
server.resource(
|
|
710
|
+
'ssr-ssg-guide',
|
|
711
|
+
'what://docs/ssr-ssg',
|
|
712
|
+
{ description: 'Server-side rendering, static site generation, and hybrid rendering in What Framework' },
|
|
713
|
+
async () => ({
|
|
714
|
+
contents: [{
|
|
715
|
+
uri: 'what://docs/ssr-ssg',
|
|
716
|
+
mimeType: 'text/markdown',
|
|
717
|
+
text: `# What Framework SSR / SSG
|
|
718
|
+
|
|
719
|
+
## Render Modes
|
|
720
|
+
|
|
721
|
+
| Mode | Description |
|
|
722
|
+
|------|-------------|
|
|
723
|
+
| \`'static'\` | Pre-rendered at build time (SSG) |
|
|
724
|
+
| \`'server'\` | Rendered on each request (SSR) |
|
|
725
|
+
| \`'client'\` | Client-only rendering (SPA) |
|
|
726
|
+
| \`'hybrid'\` | Static shell + client hydration |
|
|
727
|
+
|
|
728
|
+
## Render to String (SSR)
|
|
729
|
+
|
|
730
|
+
\`\`\`js
|
|
731
|
+
import { renderToString } from 'what-framework/server';
|
|
732
|
+
|
|
733
|
+
const html = await renderToString(h(App));
|
|
734
|
+
\`\`\`
|
|
735
|
+
|
|
736
|
+
## Stream Rendering
|
|
737
|
+
|
|
738
|
+
\`\`\`js
|
|
739
|
+
import { renderToStream } from 'what-framework/server';
|
|
740
|
+
|
|
741
|
+
for await (const chunk of renderToStream(h(App))) {
|
|
742
|
+
response.write(chunk);
|
|
743
|
+
}
|
|
744
|
+
\`\`\`
|
|
745
|
+
|
|
746
|
+
## Per-Page Configuration
|
|
747
|
+
|
|
748
|
+
\`\`\`js
|
|
749
|
+
import { definePage } from 'what-framework/server';
|
|
750
|
+
|
|
751
|
+
export const page = definePage({
|
|
752
|
+
mode: 'static', // 'static' | 'server' | 'client' | 'hybrid'
|
|
753
|
+
});
|
|
754
|
+
\`\`\`
|
|
755
|
+
|
|
756
|
+
## Server-Only Components
|
|
757
|
+
|
|
758
|
+
\`\`\`js
|
|
759
|
+
import { server } from 'what-framework/server';
|
|
760
|
+
|
|
761
|
+
const Header = server(({ title }) => h('header', null, title));
|
|
762
|
+
// This component never ships JS to the client
|
|
763
|
+
\`\`\`
|
|
764
|
+
|
|
765
|
+
## Islands + SSR
|
|
766
|
+
|
|
767
|
+
Combine SSR with islands for zero-JS-by-default pages that hydrate interactive parts on demand:
|
|
768
|
+
|
|
769
|
+
\`\`\`js
|
|
770
|
+
import { island, Island } from 'what-framework/server';
|
|
771
|
+
|
|
772
|
+
island('cart', () => import('./islands/cart.js'), {
|
|
773
|
+
mode: 'action', // Hydrate on first interaction
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
function Page() {
|
|
777
|
+
return h('div', null,
|
|
778
|
+
h('nav', null, 'Static nav — no JS'),
|
|
779
|
+
h(Island, { name: 'cart' }),
|
|
780
|
+
h('footer', null, 'Static footer — no JS'),
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
\`\`\`
|
|
784
|
+
|
|
785
|
+
### Hydration Modes
|
|
786
|
+
- \`'idle'\`: Hydrate when browser is idle
|
|
787
|
+
- \`'visible'\`: Hydrate when visible (IntersectionObserver)
|
|
788
|
+
- \`'action'\`: Hydrate on first click/focus
|
|
789
|
+
- \`'media'\`: Hydrate when media query matches
|
|
790
|
+
- \`'load'\`: Hydrate immediately
|
|
791
|
+
- \`'static'\`: Never hydrate (server only)
|
|
792
|
+
`,
|
|
793
|
+
}],
|
|
794
|
+
})
|
|
795
|
+
);
|
|
796
|
+
|
|
797
|
+
// --- CLI Resource (ported from mcp-server) ---
|
|
798
|
+
server.resource(
|
|
799
|
+
'cli-guide',
|
|
800
|
+
'what://docs/cli',
|
|
801
|
+
{ description: 'What Framework CLI commands and project configuration' },
|
|
802
|
+
async () => ({
|
|
803
|
+
contents: [{
|
|
804
|
+
uri: 'what://docs/cli',
|
|
805
|
+
mimeType: 'text/markdown',
|
|
806
|
+
text: `# What Framework CLI
|
|
807
|
+
|
|
808
|
+
## Commands
|
|
809
|
+
|
|
810
|
+
\`\`\`bash
|
|
811
|
+
what dev # Dev server with HMR
|
|
812
|
+
what build # Production build
|
|
813
|
+
what preview # Preview production build
|
|
814
|
+
what generate # Static site generation
|
|
815
|
+
\`\`\`
|
|
816
|
+
|
|
817
|
+
## Configuration
|
|
818
|
+
|
|
819
|
+
\`\`\`js
|
|
820
|
+
// what.config.js
|
|
821
|
+
export default {
|
|
822
|
+
mode: 'hybrid', // 'static' | 'server' | 'client' | 'hybrid'
|
|
823
|
+
pagesDir: 'src/pages',
|
|
824
|
+
outDir: 'dist',
|
|
825
|
+
islands: true,
|
|
826
|
+
port: 3000,
|
|
827
|
+
};
|
|
828
|
+
\`\`\`
|
|
829
|
+
|
|
830
|
+
## Environment Variables
|
|
831
|
+
|
|
832
|
+
- \`WHAT_MCP_PORT\` — Port for the devtools MCP bridge (default: 9229)
|
|
833
|
+
- \`NODE_ENV\` — \`'development'\` enables devtools instrumentation and error collection
|
|
834
|
+
`,
|
|
835
|
+
}],
|
|
836
|
+
})
|
|
837
|
+
);
|
|
838
|
+
|
|
839
|
+
// --- Testing Resource ---
|
|
840
|
+
server.resource(
|
|
841
|
+
'testing-guide',
|
|
842
|
+
'what://docs/testing',
|
|
843
|
+
{ description: 'How to test What Framework components, signals, and effects' },
|
|
844
|
+
async () => ({
|
|
845
|
+
contents: [{
|
|
846
|
+
uri: 'what://docs/testing',
|
|
847
|
+
mimeType: 'text/markdown',
|
|
848
|
+
text: `# Testing What Framework Apps
|
|
849
|
+
|
|
850
|
+
## Unit Testing Signals
|
|
851
|
+
|
|
852
|
+
\`\`\`js
|
|
853
|
+
import { signal, computed, effect, flushSync } from 'what-framework';
|
|
854
|
+
import { describe, it, assert } from 'node:test';
|
|
855
|
+
|
|
856
|
+
describe('Counter signal', () => {
|
|
857
|
+
it('increments', () => {
|
|
858
|
+
const count = signal(0);
|
|
859
|
+
count(1);
|
|
860
|
+
assert.strictEqual(count(), 1);
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
it('computed derives correctly', () => {
|
|
864
|
+
const count = signal(2);
|
|
865
|
+
const doubled = computed(() => count() * 2);
|
|
866
|
+
assert.strictEqual(doubled(), 4);
|
|
867
|
+
count(5);
|
|
868
|
+
assert.strictEqual(doubled(), 10);
|
|
869
|
+
});
|
|
870
|
+
});
|
|
871
|
+
\`\`\`
|
|
872
|
+
|
|
873
|
+
## Testing Effects
|
|
874
|
+
|
|
875
|
+
Effects flush asynchronously via microtask. Use \`flushSync()\` to force synchronous execution in tests:
|
|
876
|
+
|
|
877
|
+
\`\`\`js
|
|
878
|
+
import { signal, effect, flushSync } from 'what-framework';
|
|
879
|
+
|
|
880
|
+
it('effect tracks signal changes', () => {
|
|
881
|
+
const name = signal('Alice');
|
|
882
|
+
let captured = '';
|
|
883
|
+
effect(() => { captured = name(); });
|
|
884
|
+
flushSync();
|
|
885
|
+
assert.strictEqual(captured, 'Alice');
|
|
886
|
+
|
|
887
|
+
name('Bob');
|
|
888
|
+
flushSync();
|
|
889
|
+
assert.strictEqual(captured, 'Bob');
|
|
890
|
+
});
|
|
891
|
+
\`\`\`
|
|
892
|
+
|
|
893
|
+
## Testing Components
|
|
894
|
+
|
|
895
|
+
\`\`\`js
|
|
896
|
+
import { mount } from 'what-framework';
|
|
897
|
+
|
|
898
|
+
it('renders component', () => {
|
|
899
|
+
const container = document.createElement('div');
|
|
900
|
+
mount(<Counter />, container);
|
|
901
|
+
assert.ok(container.querySelector('button'));
|
|
902
|
+
assert.strictEqual(container.textContent.includes('0'), true);
|
|
903
|
+
});
|
|
904
|
+
\`\`\`
|
|
905
|
+
|
|
906
|
+
## Testing with MCP Devtools
|
|
907
|
+
|
|
908
|
+
Use the MCP tools for integration-level debugging:
|
|
909
|
+
1. \`what_lint { code: "..." }\` — static analysis on code before running
|
|
910
|
+
2. \`what_validate { code: "..." }\` — compile check + lint in one call
|
|
911
|
+
3. \`what_snapshot { diff: true }\` — verify state changes after an action
|
|
912
|
+
`,
|
|
913
|
+
}],
|
|
914
|
+
})
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
// --- Project Structure Resource ---
|
|
918
|
+
server.resource(
|
|
919
|
+
'project-structure',
|
|
920
|
+
'what://docs/project-structure',
|
|
921
|
+
{ description: 'Recommended project structure and file conventions for What Framework apps' },
|
|
922
|
+
async () => ({
|
|
923
|
+
contents: [{
|
|
924
|
+
uri: 'what://docs/project-structure',
|
|
925
|
+
mimeType: 'text/markdown',
|
|
926
|
+
text: `# What Framework Project Structure
|
|
927
|
+
|
|
928
|
+
## Recommended Layout
|
|
929
|
+
|
|
930
|
+
\`\`\`
|
|
931
|
+
my-app/
|
|
932
|
+
what.config.js # Framework configuration
|
|
933
|
+
src/
|
|
934
|
+
pages/ # File-based routes (auto-discovered)
|
|
935
|
+
index.jsx # /
|
|
936
|
+
about.jsx # /about
|
|
937
|
+
users/
|
|
938
|
+
[id].jsx # /users/:id
|
|
939
|
+
index.jsx # /users
|
|
940
|
+
components/ # Shared components
|
|
941
|
+
Header.jsx
|
|
942
|
+
Footer.jsx
|
|
943
|
+
islands/ # Interactive islands (hydrated on demand)
|
|
944
|
+
Cart.jsx
|
|
945
|
+
SearchBar.jsx
|
|
946
|
+
stores/ # Global stores and shared state
|
|
947
|
+
auth.js
|
|
948
|
+
cart.js
|
|
949
|
+
lib/ # Utilities, API clients, helpers
|
|
950
|
+
api.js
|
|
951
|
+
format.js
|
|
952
|
+
styles/ # Global styles
|
|
953
|
+
global.css
|
|
954
|
+
public/ # Static assets (copied as-is)
|
|
955
|
+
favicon.ico
|
|
956
|
+
dist/ # Build output (generated)
|
|
957
|
+
\`\`\`
|
|
958
|
+
|
|
959
|
+
## Naming Conventions
|
|
960
|
+
|
|
961
|
+
- **Components**: PascalCase (\`MyComponent.jsx\`)
|
|
962
|
+
- **Pages**: lowercase or kebab-case (\`about.jsx\`, \`blog-post.jsx\`)
|
|
963
|
+
- **Islands**: PascalCase, in \`islands/\` directory, export \`.island = true\`
|
|
964
|
+
- **Stores**: camelCase (\`authStore.js\`)
|
|
965
|
+
- **Signals**: camelCase variable names with optional debug name: \`signal(0, 'count')\`
|
|
966
|
+
|
|
967
|
+
## Import Conventions
|
|
968
|
+
|
|
969
|
+
Always import from \`'what-framework'\`, not \`'what'\`:
|
|
970
|
+
|
|
971
|
+
\`\`\`js
|
|
972
|
+
import { signal, effect, computed, onMount } from 'what-framework';
|
|
973
|
+
import { Router, Link, navigate } from 'what-framework/router';
|
|
974
|
+
import { renderToString, definePage } from 'what-framework/server';
|
|
975
|
+
\`\`\`
|
|
976
|
+
`,
|
|
977
|
+
}],
|
|
978
|
+
})
|
|
979
|
+
);
|
|
980
|
+
|
|
981
|
+
// --- Tool Pipeline Guide Resource ---
|
|
982
|
+
server.resource(
|
|
983
|
+
'tool-pipelines',
|
|
984
|
+
'what://docs/tool-pipelines',
|
|
985
|
+
{ description: 'How to call MCP tools efficiently: recommended pipelines, token costs, anti-patterns, cascade rules' },
|
|
986
|
+
async () => {
|
|
987
|
+
// Load from the markdown file at build time
|
|
988
|
+
let text;
|
|
989
|
+
try {
|
|
990
|
+
const { readFileSync } = await import('node:fs');
|
|
991
|
+
const { fileURLToPath } = await import('node:url');
|
|
992
|
+
const path = fileURLToPath(new URL('../TOOL-PIPELINES.md', import.meta.url));
|
|
993
|
+
text = readFileSync(path, 'utf-8');
|
|
994
|
+
} catch {
|
|
995
|
+
text = '# Tool Pipeline Guide\n\nFailed to load TOOL-PIPELINES.md. Check the package installation.';
|
|
996
|
+
}
|
|
997
|
+
return {
|
|
998
|
+
contents: [{
|
|
999
|
+
uri: 'what://docs/tool-pipelines',
|
|
1000
|
+
mimeType: 'text/markdown',
|
|
1001
|
+
text,
|
|
1002
|
+
}],
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
);
|
|
1006
|
+
|
|
620
1007
|
// Import and register extended tools if available
|
|
621
1008
|
try {
|
|
622
1009
|
const { registerExtendedTools } = await import('./tools-extended.js');
|
|
@@ -633,5 +1020,68 @@ try {
|
|
|
633
1020
|
// Agent tools not yet available — that's fine
|
|
634
1021
|
}
|
|
635
1022
|
|
|
1023
|
+
// --- MCP Prompts: agent guidance ---
|
|
1024
|
+
|
|
1025
|
+
server.prompt(
|
|
1026
|
+
'what-devtools-guide',
|
|
1027
|
+
'How to use WhatFW MCP DevTools effectively. Read this first.',
|
|
1028
|
+
{},
|
|
1029
|
+
async () => ({
|
|
1030
|
+
messages: [{
|
|
1031
|
+
role: 'user',
|
|
1032
|
+
content: {
|
|
1033
|
+
type: 'text',
|
|
1034
|
+
text: `# WhatFW MCP DevTools — Quick Reference
|
|
1035
|
+
|
|
1036
|
+
You have access to 28 MCP tools for inspecting and debugging a live What Framework app running in the browser.
|
|
1037
|
+
|
|
1038
|
+
## Connection Check
|
|
1039
|
+
Always start with: \`what_connection_status\` — confirms the browser is connected and shows signal/effect/component counts.
|
|
1040
|
+
|
|
1041
|
+
## Top 5 Tools (use these most)
|
|
1042
|
+
|
|
1043
|
+
1. **what_diagnose** — One-call health check. Finds errors, performance issues, and reactivity problems.
|
|
1044
|
+
2. **what_explain** {componentId} — Everything about one component: its signals, effects, DOM, and errors.
|
|
1045
|
+
3. **what_look** {componentId} — Visual inspection WITHOUT a screenshot: computed styles, dimensions, layout classification, child elements, accessibility.
|
|
1046
|
+
4. **what_signals** — List all reactive signals with current values. Filter by name.
|
|
1047
|
+
5. **what_lint** {code} — Static analysis before saving code. Catches 7 common mistakes.
|
|
1048
|
+
|
|
1049
|
+
## Visual Tools (cheapest first)
|
|
1050
|
+
- \`what_look\` — Text description of styles/layout (~400 tokens). Use FIRST.
|
|
1051
|
+
- \`what_page_map\` — Full page skeleton with landmarks, buttons, headings (~800 tokens).
|
|
1052
|
+
- \`what_screenshot\` {componentId} — Cropped image of ONE component (5-20KB). Use only if text isn't enough.
|
|
1053
|
+
|
|
1054
|
+
## State Debugging
|
|
1055
|
+
- \`what_signals\` — See all signal values
|
|
1056
|
+
- \`what_signal_trace\` {signalId} — "Why did this signal change?" Shows which effects wrote to it.
|
|
1057
|
+
- \`what_dependency_graph\` {signalId} — Full reactive graph: signal → effects → downstream.
|
|
1058
|
+
- \`what_watch\` — Observe reactive events over a time window.
|
|
1059
|
+
|
|
1060
|
+
## Actions
|
|
1061
|
+
- \`what_set_signal\` {signalId, value} — Directly change a signal value in the live app.
|
|
1062
|
+
- \`what_navigate\` {path} — Navigate to a different route.
|
|
1063
|
+
|
|
1064
|
+
## Code Quality
|
|
1065
|
+
- \`what_lint\` — Check code for signal-read-without-(), effect cycles, missing cleanup, etc.
|
|
1066
|
+
- \`what_scaffold\` {type, name} — Generate idiomatic component/page/form/store boilerplate.
|
|
1067
|
+
- \`what_fix\` {errorCode} — Get diagnosis + fix + code example for any WhatFW error.
|
|
1068
|
+
|
|
1069
|
+
## Anti-Patterns
|
|
1070
|
+
- DON'T screenshot first — use what_look (10x cheaper)
|
|
1071
|
+
- DON'T call what_signals + what_effects + what_dom_inspect separately — use what_explain
|
|
1072
|
+
- DON'T use what_eval for state inspection — use the structured tools
|
|
1073
|
+
|
|
1074
|
+
## What Framework Basics
|
|
1075
|
+
- Components run ONCE (not on every render like React)
|
|
1076
|
+
- \`signal(value, 'name')\` for state — read with \`sig()\`, write with \`sig(newValue)\`
|
|
1077
|
+
- \`effect(() => { ... })\` for side effects — auto-tracks signal reads
|
|
1078
|
+
- \`computed(() => ...)\` for derived values — lazy, cached
|
|
1079
|
+
- Import from \`'what-framework'\`
|
|
1080
|
+
`,
|
|
1081
|
+
},
|
|
1082
|
+
}],
|
|
1083
|
+
})
|
|
1084
|
+
);
|
|
1085
|
+
|
|
636
1086
|
const transport = new StdioServerTransport();
|
|
637
1087
|
await server.connect(transport);
|