what-devtools-mcp 0.11.8 → 0.12.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "what-devtools-mcp",
3
- "version": "0.11.8",
3
+ "version": "0.12.0",
4
4
  "description": "MCP server bridging AI agents to live What Framework app state via WebSocket",
5
5
  "type": "module",
6
6
  "bin": {
package/src/index.js CHANGED
@@ -9,6 +9,7 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9
9
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
10
10
  import { createBridge } from './bridge.js';
11
11
  import { registerTools } from './tools.js';
12
+ import { instrumentServer } from './tool-registry.js';
12
13
 
13
14
  const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf-8'));
14
15
 
@@ -52,6 +53,10 @@ const server = new McpServer({
52
53
  version: pkg.version,
53
54
  });
54
55
 
56
+ // Record every registration so what_connection_status can report the real
57
+ // catalogue instead of a hand-maintained literal. Must run before any
58
+ // register* call.
59
+ instrumentServer(server);
55
60
  registerTools(server, bridge);
56
61
 
57
62
  // --- MCP Resources: static docs for agent context ---
@@ -0,0 +1,69 @@
1
+ /**
2
+ * The single source of truth for what tools this MCP server actually exposes.
3
+ *
4
+ * `what_connection_status` is the tool CLAUDE.md tells every agent to call FIRST
5
+ * to orient, and it used to answer with a hand-maintained literal array of 17
6
+ * entries while 29 tools were registered. The product under-reported its own
7
+ * differentiator by 41% at the moment of first contact, and the same catalogue
8
+ * was hand-copied into CLAUDE.md, AGENTS.md and the docs, each free to drift.
9
+ *
10
+ * A framework whose thesis is machine-readable truth cannot hand-maintain its own
11
+ * machine-readable index, so the catalogue is now derived from registration by
12
+ * wrapping `server.tool` once before the three register* functions run.
13
+ */
14
+
15
+ const registered = [];
16
+
17
+ // Tools that answer with no browser attached. Everything else needs a live page
18
+ // on the bridge, which is a stronger requirement than "the dev server is up" and
19
+ // is the single most common reason an agent's first call comes back empty.
20
+ const OFFLINE_TOOLS = new Set([
21
+ 'what_connection_status',
22
+ 'what_lint',
23
+ 'what_validate',
24
+ 'what_scaffold',
25
+ 'what_fix',
26
+ ]);
27
+
28
+ /**
29
+ * Wrap `server.tool` so every registration is recorded. Call once, before any
30
+ * register* function runs. Idempotent.
31
+ */
32
+ export function instrumentServer(server) {
33
+ if (server.__whatToolRegistryInstalled) return server;
34
+ const original = server.tool.bind(server);
35
+ server.tool = (name, description, ...rest) => {
36
+ registered.push({ name, desc: description });
37
+ return original(name, description, ...rest);
38
+ };
39
+ server.__whatToolRegistryInstalled = true;
40
+ return server;
41
+ }
42
+
43
+ /** Every tool registered so far, in registration order. */
44
+ export function getRegisteredTools() {
45
+ return registered.map((t) => ({ ...t }));
46
+ }
47
+
48
+ /**
49
+ * Split the catalogue by what can actually answer right now.
50
+ *
51
+ * No competitor's MCP server distinguishes "this tool exists" from "this tool can
52
+ * answer in this session", which is the distinction that actually costs an agent
53
+ * turns: it discovers the difference by calling and failing.
54
+ */
55
+ export function describeToolAvailability(connected) {
56
+ const all = getRegisteredTools();
57
+ const offline = all.filter((t) => OFFLINE_TOOLS.has(t.name));
58
+ const needsBrowser = all.filter((t) => !OFFLINE_TOOLS.has(t.name));
59
+ return {
60
+ total: all.length,
61
+ available: connected ? all : offline,
62
+ requiresBrowser: connected ? [] : needsBrowser,
63
+ };
64
+ }
65
+
66
+ /** Test hook: drop the recorded registrations. @internal */
67
+ export function __resetToolRegistry() {
68
+ registered.length = 0;
69
+ }
package/src/tools.js CHANGED
@@ -4,6 +4,7 @@
4
4
  */
5
5
 
6
6
  import { z } from 'zod';
7
+ import { describeToolAvailability } from './tool-registry.js';
7
8
 
8
9
  export function registerTools(server, bridge) {
9
10
  // --- Helpers ---
@@ -112,26 +113,14 @@ export function registerTools(server, bridge) {
112
113
  'Make sure your app is running with the what-devtools-mcp Vite plugin',
113
114
  'Or manually call connectDevToolsMCP() in your browser console',
114
115
  ],
115
- // Tool catalog so agents know what's available
116
- tools: [
117
- { name: 'what_components', desc: 'List mounted components with IDs' },
118
- { name: 'what_signals', desc: 'List signals with values (use filter!)' },
119
- { name: 'what_effects', desc: 'List effects with deps and run counts' },
120
- { name: 'what_explain', desc: 'Everything about one component (signals + effects + DOM + errors)' },
121
- { name: 'what_look', desc: 'Visual info without image: styles, layout, dimensions' },
122
- { name: 'what_screenshot', desc: 'Cropped component screenshot (5-20KB)' },
123
- { name: 'what_page_map', desc: 'Full page layout skeleton' },
124
- { name: 'what_diagnose', desc: 'One-call health check (errors + perf + reactivity)' },
125
- { name: 'what_errors', desc: 'Runtime errors with fix suggestions' },
126
- { name: 'what_signal_trace', desc: 'Why did a signal change? Causal chain.' },
127
- { name: 'what_dependency_graph', desc: 'Reactive dependency graph' },
128
- { name: 'what_watch', desc: 'Observe events over a time window' },
129
- { name: 'what_record_window', desc: 'Rank effects that re-ran during a recording window — what fired for this action?' },
130
- { name: 'what_set_signal', desc: 'Change a signal value in the live app' },
131
- { name: 'what_lint', desc: 'Static analysis for code (no browser needed)' },
132
- { name: 'what_scaffold', desc: 'Generate boilerplate (no browser needed)' },
133
- { name: 'what_fix', desc: 'Error diagnosis with code examples (no browser needed)' },
134
- ],
116
+ // Tool catalogue, DERIVED from registration rather than hand-maintained.
117
+ // `tools` is every registered tool; `available` is the subset that can
118
+ // answer right now, which is the distinction that actually costs an
119
+ // agent turns when no browser is attached.
120
+ ...(() => {
121
+ const a = describeToolAvailability(connected);
122
+ return { toolCount: a.total, tools: a.available, requiresBrowser: a.requiresBrowser.map((t) => t.name) };
123
+ })(),
135
124
  };
136
125
 
137
126
  return {