@llui/mcp 0.0.11 → 0.0.13
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 +46 -0
- package/dist/index.d.ts +19 -21
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -462
- package/dist/index.js.map +1 -1
- package/dist/tool-registry.d.ts +31 -0
- package/dist/tool-registry.d.ts.map +1 -0
- package/dist/tool-registry.js +22 -0
- package/dist/tool-registry.js.map +1 -0
- package/dist/tools/debug-api.d.ts +9 -0
- package/dist/tools/debug-api.d.ts.map +1 -0
- package/dist/tools/debug-api.js +627 -0
- package/dist/tools/debug-api.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/replay-test-generator.d.ts +9 -0
- package/dist/tools/replay-test-generator.d.ts.map +1 -0
- package/dist/tools/replay-test-generator.js +22 -0
- package/dist/tools/replay-test-generator.js.map +1 -0
- package/dist/transports/index.d.ts +3 -0
- package/dist/transports/index.d.ts.map +1 -0
- package/dist/transports/index.js +2 -0
- package/dist/transports/index.js.map +1 -0
- package/dist/transports/relay.d.ts +24 -0
- package/dist/transports/relay.d.ts.map +1 -0
- package/dist/transports/relay.js +86 -0
- package/dist/transports/relay.js.map +1 -0
- package/dist/util/diff.d.ts +21 -0
- package/dist/util/diff.d.ts.map +1 -0
- package/dist/util/diff.js +36 -0
- package/dist/util/diff.js.map +1 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { WebSocketServer } from 'ws';
|
|
3
|
-
import { randomUUID } from 'node:crypto';
|
|
4
|
-
import { mkdirSync, writeFileSync, unlinkSync, existsSync, readFileSync } from 'node:fs';
|
|
1
|
+
import { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';
|
|
5
2
|
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { ToolRegistry } from './tool-registry.js';
|
|
4
|
+
import { registerDebugApiTools } from './tools/index.js';
|
|
5
|
+
import { WebSocketRelayTransport } from './transports/index.js';
|
|
6
6
|
/**
|
|
7
7
|
* Walk up from `start` until we find a workspace root marker. Used by
|
|
8
8
|
* both the MCP server (writing the active marker) and the Vite plugin
|
|
@@ -47,287 +47,32 @@ export function findWorkspaceRoot(start = process.cwd()) {
|
|
|
47
47
|
export function mcpActiveFilePath(cwd = process.cwd()) {
|
|
48
48
|
return resolve(findWorkspaceRoot(cwd), 'node_modules/.cache/llui-mcp/active.json');
|
|
49
49
|
}
|
|
50
|
-
// ──
|
|
51
|
-
const TOOLS = [
|
|
52
|
-
{
|
|
53
|
-
name: 'llui_get_state',
|
|
54
|
-
description: 'Get the current state of the LLui component. Returns a JSON-serializable state object.',
|
|
55
|
-
inputSchema: {
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {
|
|
58
|
-
component: {
|
|
59
|
-
type: 'string',
|
|
60
|
-
description: 'Component name (defaults to root)',
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
{
|
|
66
|
-
name: 'llui_send_message',
|
|
67
|
-
description: 'Send a message to the component and return the new state and effects. Validates the message first. Calls flush() automatically.',
|
|
68
|
-
inputSchema: {
|
|
69
|
-
type: 'object',
|
|
70
|
-
properties: {
|
|
71
|
-
msg: {
|
|
72
|
-
type: 'object',
|
|
73
|
-
description: 'The message to send (must be a valid Msg variant)',
|
|
74
|
-
},
|
|
75
|
-
},
|
|
76
|
-
required: ['msg'],
|
|
77
|
-
},
|
|
78
|
-
},
|
|
79
|
-
{
|
|
80
|
-
name: 'llui_eval_update',
|
|
81
|
-
description: 'Dry-run: call update(state, msg) without applying. Returns what the new state and effects would be without modifying the running app.',
|
|
82
|
-
inputSchema: {
|
|
83
|
-
type: 'object',
|
|
84
|
-
properties: {
|
|
85
|
-
msg: {
|
|
86
|
-
type: 'object',
|
|
87
|
-
description: 'The hypothetical message to evaluate',
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
required: ['msg'],
|
|
91
|
-
},
|
|
92
|
-
},
|
|
93
|
-
{
|
|
94
|
-
name: 'llui_validate_message',
|
|
95
|
-
description: 'Validate a message against the component Msg type. Returns errors or null if valid.',
|
|
96
|
-
inputSchema: {
|
|
97
|
-
type: 'object',
|
|
98
|
-
properties: {
|
|
99
|
-
msg: {
|
|
100
|
-
type: 'object',
|
|
101
|
-
description: 'The message to validate',
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
required: ['msg'],
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: 'llui_get_message_history',
|
|
109
|
-
description: 'Get the chronological message history with state transitions, effects, and dirty masks. Supports pagination via `since` (exclusive, return entries with index > since) and `limit` (return at most N most-recent entries). Use both together for tail-fetching.',
|
|
110
|
-
inputSchema: {
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {
|
|
113
|
-
since: {
|
|
114
|
-
type: 'number',
|
|
115
|
-
description: 'Return entries with index strictly greater than this.',
|
|
116
|
-
},
|
|
117
|
-
limit: {
|
|
118
|
-
type: 'number',
|
|
119
|
-
description: 'Max entries to return (the N most recent).',
|
|
120
|
-
},
|
|
121
|
-
},
|
|
122
|
-
},
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
name: 'llui_export_trace',
|
|
126
|
-
description: 'Export the current session as a replayable LluiTrace JSON.',
|
|
127
|
-
inputSchema: {
|
|
128
|
-
type: 'object',
|
|
129
|
-
properties: {},
|
|
130
|
-
},
|
|
131
|
-
},
|
|
132
|
-
{
|
|
133
|
-
name: 'llui_get_bindings',
|
|
134
|
-
description: 'Get all active reactive bindings with their masks, last values, and DOM targets.',
|
|
135
|
-
inputSchema: {
|
|
136
|
-
type: 'object',
|
|
137
|
-
properties: {
|
|
138
|
-
filter: {
|
|
139
|
-
type: 'string',
|
|
140
|
-
description: 'Filter by DOM selector or mask value',
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
},
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
name: 'llui_why_did_update',
|
|
147
|
-
description: 'Explain why a specific binding re-evaluated: which mask bits were dirty, what the accessor returned, what the previous value was.',
|
|
148
|
-
inputSchema: {
|
|
149
|
-
type: 'object',
|
|
150
|
-
properties: {
|
|
151
|
-
bindingIndex: {
|
|
152
|
-
type: 'number',
|
|
153
|
-
description: 'The binding index to inspect',
|
|
154
|
-
},
|
|
155
|
-
},
|
|
156
|
-
required: ['bindingIndex'],
|
|
157
|
-
},
|
|
158
|
-
},
|
|
159
|
-
{
|
|
160
|
-
name: 'llui_search_state',
|
|
161
|
-
description: 'Search current state using a dot-separated path query. E.g., "cart.items" returns the items array.',
|
|
162
|
-
inputSchema: {
|
|
163
|
-
type: 'object',
|
|
164
|
-
properties: {
|
|
165
|
-
query: {
|
|
166
|
-
type: 'string',
|
|
167
|
-
description: 'Dot-separated path to search. E.g., "user.name", "items"',
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
required: ['query'],
|
|
171
|
-
},
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
name: 'llui_clear_log',
|
|
175
|
-
description: 'Clear the message and effects history.',
|
|
176
|
-
inputSchema: {
|
|
177
|
-
type: 'object',
|
|
178
|
-
properties: {},
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
{
|
|
182
|
-
name: 'llui_list_messages',
|
|
183
|
-
description: 'List all message variants the component accepts, with their field types. Returns { discriminant, variants: { [name]: { [field]: typeDescriptor } } }. Use this to discover what messages can be sent without reading source code.',
|
|
184
|
-
inputSchema: {
|
|
185
|
-
type: 'object',
|
|
186
|
-
properties: {},
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
{
|
|
190
|
-
name: 'llui_decode_mask',
|
|
191
|
-
description: "Decode a dirty-mask value from llui_get_message_history (the 'dirtyMask' field) into the list of top-level state fields that changed. Requires 'mask' param.",
|
|
192
|
-
inputSchema: {
|
|
193
|
-
type: 'object',
|
|
194
|
-
properties: {
|
|
195
|
-
mask: { type: 'number', description: 'The dirtyMask value to decode' },
|
|
196
|
-
},
|
|
197
|
-
required: ['mask'],
|
|
198
|
-
},
|
|
199
|
-
},
|
|
200
|
-
{
|
|
201
|
-
name: 'llui_mask_legend',
|
|
202
|
-
description: 'Return the compiler-generated bit→field map for this component. Example: { todos: 1, filter: 2, nextId: 4 } means bit 0 represents `todos`, etc.',
|
|
203
|
-
inputSchema: {
|
|
204
|
-
type: 'object',
|
|
205
|
-
properties: {},
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
name: 'llui_component_info',
|
|
210
|
-
description: 'Get component name and source location (file + line) of the component() declaration. Lets you find where to read or edit the component.',
|
|
211
|
-
inputSchema: {
|
|
212
|
-
type: 'object',
|
|
213
|
-
properties: {},
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: 'llui_describe_state',
|
|
218
|
-
description: "Return the State type's shape (not its value). Fields map to type descriptors: 'string', 'number', 'boolean', {kind:'enum',values:[...]}, {kind:'array',of:...}, {kind:'object',fields:...}, {kind:'optional',of:...}. Use this to know what fields exist and their types even when currently undefined.",
|
|
219
|
-
inputSchema: {
|
|
220
|
-
type: 'object',
|
|
221
|
-
properties: {},
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
{
|
|
225
|
-
name: 'llui_list_effects',
|
|
226
|
-
description: 'List all effect variants the component emits, with their field types (same format as llui_list_messages). Returns null if no Effect type is declared.',
|
|
227
|
-
inputSchema: {
|
|
228
|
-
type: 'object',
|
|
229
|
-
properties: {},
|
|
230
|
-
},
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
name: 'llui_trace_element',
|
|
234
|
-
description: "Find all bindings targeting a DOM element matched by a CSS selector. Returns { bindingIndex, kind, key, mask, lastValue, relation }[] so you can answer 'why is this element wrong?' — combine with llui_why_did_update(bindingIndex) for a full narrative.",
|
|
235
|
-
inputSchema: {
|
|
236
|
-
type: 'object',
|
|
237
|
-
properties: {
|
|
238
|
-
selector: { type: 'string', description: 'CSS selector (e.g. `.todo.active`, `#submit`)' },
|
|
239
|
-
},
|
|
240
|
-
required: ['selector'],
|
|
241
|
-
},
|
|
242
|
-
},
|
|
243
|
-
{
|
|
244
|
-
name: 'llui_snapshot_state',
|
|
245
|
-
description: 'Capture the current state (deep clone). Returns the snapshot — store it, then call llui_restore_state later to roll back. Useful for safely exploring transitions during a debugging session.',
|
|
246
|
-
inputSchema: { type: 'object', properties: {} },
|
|
247
|
-
},
|
|
248
|
-
{
|
|
249
|
-
name: 'llui_restore_state',
|
|
250
|
-
description: 'Overwrite the current state with a previously-captured snapshot. Triggers a full re-render (FULL_MASK). Bypasses update() — snap must already be a valid state value.',
|
|
251
|
-
inputSchema: {
|
|
252
|
-
type: 'object',
|
|
253
|
-
properties: {
|
|
254
|
-
snapshot: {
|
|
255
|
-
description: 'The state object returned by llui_snapshot_state.',
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
required: ['snapshot'],
|
|
259
|
-
},
|
|
260
|
-
},
|
|
261
|
-
{
|
|
262
|
-
name: 'llui_list_components',
|
|
263
|
-
description: 'List all currently-mounted LLui components + which one is active (being targeted by subsequent tool calls). Multi-mount apps show one entry per mount.',
|
|
264
|
-
inputSchema: { type: 'object', properties: {} },
|
|
265
|
-
},
|
|
266
|
-
{
|
|
267
|
-
name: 'llui_select_component',
|
|
268
|
-
description: 'Switch the active component (the one all other tool calls target). Use a key from llui_list_components.',
|
|
269
|
-
inputSchema: {
|
|
270
|
-
type: 'object',
|
|
271
|
-
properties: {
|
|
272
|
-
key: { type: 'string', description: 'Component key as returned by llui_list_components' },
|
|
273
|
-
},
|
|
274
|
-
required: ['key'],
|
|
275
|
-
},
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
name: 'llui_replay_trace',
|
|
279
|
-
description: 'Generate a ready-to-run vitest file that replays the current message history via `replayTrace()` from @llui/test. The output is a complete test file with the trace inlined — paste it into packages/<pkg>/test/ to reproduce the exact sequence of messages the component saw in this session. Use this to capture a debugging session as a regression test.',
|
|
280
|
-
inputSchema: {
|
|
281
|
-
type: 'object',
|
|
282
|
-
properties: {
|
|
283
|
-
importPath: {
|
|
284
|
-
type: 'string',
|
|
285
|
-
description: "Where to import the component def from in the generated test (default: '../src/index'). Example: '../src/todo-app'.",
|
|
286
|
-
},
|
|
287
|
-
exportName: {
|
|
288
|
-
type: 'string',
|
|
289
|
-
description: "Named export that holds the component def (default: the component's name).",
|
|
290
|
-
},
|
|
291
|
-
},
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
{
|
|
295
|
-
name: 'llui_lint',
|
|
296
|
-
description: "Lint LLui source code against @llui/lint-idiomatic's 17 anti-pattern rules. Returns violations grouped by rule with line/column/suggestion fields, plus a 0–17 score (17 = fully idiomatic). Pass either `source` (raw TypeScript code) or `path` (absolute file path on the dev machine) — exactly one is required. The optional `exclude` array skips specific rule names. Use this after writing or editing LLui code to self-correct: catches state mutation, missing memo(), each() closure violations, view-bag-import (use the bag inside component bodies, see llm-guide.md), missing exhaustive update() cases, async update() (must be sync), nested send() in update(), spread-in-children (use each() instead), imperative DOM in view(), and more. The same rules run as a Vite plugin in dev — this tool gives LLMs the same feedback without requiring a build.",
|
|
297
|
-
inputSchema: {
|
|
298
|
-
type: 'object',
|
|
299
|
-
properties: {
|
|
300
|
-
source: {
|
|
301
|
-
type: 'string',
|
|
302
|
-
description: 'TypeScript source code to lint. Mutually exclusive with `path`.',
|
|
303
|
-
},
|
|
304
|
-
path: {
|
|
305
|
-
type: 'string',
|
|
306
|
-
description: 'Absolute file path to read and lint (must be a .ts/.tsx file). Mutually exclusive with `source`.',
|
|
307
|
-
},
|
|
308
|
-
exclude: {
|
|
309
|
-
type: 'array',
|
|
310
|
-
items: { type: 'string' },
|
|
311
|
-
description: "Rule names to skip (e.g. ['map-on-state-array']). Useful when running in a project that already gets that rule from @llui/vite-plugin's diagnose() pass.",
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
];
|
|
50
|
+
// ── MCP Server ──────────────────────────────────────────────────
|
|
317
51
|
export class LluiMcpServer {
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
/** Bridge (WebSocket) state for out-of-process browser connection. */
|
|
321
|
-
wsServer = null;
|
|
322
|
-
browserWs = null;
|
|
323
|
-
pending = new Map();
|
|
52
|
+
registry;
|
|
53
|
+
relay;
|
|
324
54
|
bridgePort;
|
|
55
|
+
devUrl = null;
|
|
325
56
|
constructor(bridgePort = 5200) {
|
|
326
57
|
this.bridgePort = bridgePort;
|
|
58
|
+
this.registry = new ToolRegistry();
|
|
59
|
+
this.relay = new WebSocketRelayTransport({ port: bridgePort });
|
|
60
|
+
registerDebugApiTools(this.registry);
|
|
327
61
|
}
|
|
328
62
|
/** Connect to a debug API instance directly (for in-process usage). */
|
|
329
63
|
connectDirect(api) {
|
|
330
|
-
this.
|
|
64
|
+
this.relay.connectDirect(api);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Set the dev-server URL that Phase 2's CDP fallback can navigate a
|
|
68
|
+
* Playwright browser to. Persisted into the active marker file so the
|
|
69
|
+
* Vite plugin (or other consumers) can rebroadcast it. If the bridge is
|
|
70
|
+
* already running, rewrites the marker so consumers see the update.
|
|
71
|
+
*/
|
|
72
|
+
setDevUrl(url) {
|
|
73
|
+
this.devUrl = url;
|
|
74
|
+
if (this.relay.isServerRunning())
|
|
75
|
+
this.writeActiveFile();
|
|
331
76
|
}
|
|
332
77
|
/**
|
|
333
78
|
* Start a WebSocket server on the configured bridge port. The browser-side
|
|
@@ -335,51 +80,26 @@ export class LluiMcpServer {
|
|
|
335
80
|
* debug-API calls.
|
|
336
81
|
*/
|
|
337
82
|
startBridge() {
|
|
338
|
-
|
|
339
|
-
return;
|
|
340
|
-
this.wsServer = new WebSocketServer({ port: this.bridgePort, host: '127.0.0.1' });
|
|
341
|
-
this.wsServer.on('connection', (ws) => {
|
|
342
|
-
this.browserWs = ws;
|
|
343
|
-
ws.on('message', (raw) => {
|
|
344
|
-
let msg;
|
|
345
|
-
try {
|
|
346
|
-
msg = JSON.parse(String(raw));
|
|
347
|
-
}
|
|
348
|
-
catch {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
const p = this.pending.get(msg.id);
|
|
352
|
-
if (!p)
|
|
353
|
-
return;
|
|
354
|
-
this.pending.delete(msg.id);
|
|
355
|
-
if (msg.error)
|
|
356
|
-
p.reject(new Error(msg.error));
|
|
357
|
-
else
|
|
358
|
-
p.resolve(msg.result);
|
|
359
|
-
});
|
|
360
|
-
ws.on('close', () => {
|
|
361
|
-
if (this.browserWs === ws)
|
|
362
|
-
this.browserWs = null;
|
|
363
|
-
});
|
|
364
|
-
});
|
|
83
|
+
this.relay.start();
|
|
365
84
|
// Write the active marker file so Vite plugins watching it can
|
|
366
85
|
// dispatch an HMR custom event to auto-trigger browser connects.
|
|
367
86
|
this.writeActiveFile();
|
|
368
87
|
}
|
|
369
88
|
stopBridge() {
|
|
370
|
-
this.
|
|
371
|
-
this.wsServer = null;
|
|
372
|
-
this.browserWs = null;
|
|
373
|
-
for (const p of this.pending.values())
|
|
374
|
-
p.reject(new Error('bridge closed'));
|
|
375
|
-
this.pending.clear();
|
|
89
|
+
this.relay.stop();
|
|
376
90
|
this.removeActiveFile();
|
|
377
91
|
}
|
|
378
92
|
writeActiveFile() {
|
|
379
93
|
try {
|
|
380
94
|
const path = mcpActiveFilePath();
|
|
381
95
|
mkdirSync(dirname(path), { recursive: true });
|
|
382
|
-
|
|
96
|
+
const payload = {
|
|
97
|
+
port: this.bridgePort,
|
|
98
|
+
pid: process.pid,
|
|
99
|
+
};
|
|
100
|
+
if (this.devUrl !== null)
|
|
101
|
+
payload.devUrl = this.devUrl;
|
|
102
|
+
writeFileSync(path, JSON.stringify(payload));
|
|
383
103
|
}
|
|
384
104
|
catch {
|
|
385
105
|
// Best-effort — failure to write the marker should not crash the server
|
|
@@ -395,139 +115,14 @@ export class LluiMcpServer {
|
|
|
395
115
|
// Ignore — file may already be gone
|
|
396
116
|
}
|
|
397
117
|
}
|
|
398
|
-
/** Invoke a debug API method — over the bridge if connected, else direct. */
|
|
399
|
-
async call(method, args) {
|
|
400
|
-
if (this.debugApi) {
|
|
401
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
402
|
-
const fn = this.debugApi[method];
|
|
403
|
-
return typeof fn === 'function' ? fn.apply(this.debugApi, args) : undefined;
|
|
404
|
-
}
|
|
405
|
-
if (!this.browserWs) {
|
|
406
|
-
throw new Error('No browser connected to the MCP bridge. Start your dev server.');
|
|
407
|
-
}
|
|
408
|
-
const id = randomUUID();
|
|
409
|
-
return new Promise((resolve, reject) => {
|
|
410
|
-
this.pending.set(id, { resolve, reject });
|
|
411
|
-
this.browserWs.send(JSON.stringify({ id, method, args }));
|
|
412
|
-
setTimeout(() => {
|
|
413
|
-
if (this.pending.delete(id))
|
|
414
|
-
reject(new Error(`timeout: ${method}`));
|
|
415
|
-
}, 5000);
|
|
416
|
-
});
|
|
417
|
-
}
|
|
418
118
|
/** Get tool definitions for MCP handshake */
|
|
419
119
|
getTools() {
|
|
420
|
-
return
|
|
120
|
+
return this.registry.listDefinitions();
|
|
421
121
|
}
|
|
422
122
|
/** Handle an MCP tool call */
|
|
423
123
|
async handleToolCall(name, args) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
return this.call('getState', []);
|
|
427
|
-
case 'llui_send_message': {
|
|
428
|
-
const errors = (await this.call('validateMessage', [args.msg]));
|
|
429
|
-
if (errors)
|
|
430
|
-
return { errors, sent: false };
|
|
431
|
-
await this.call('send', [args.msg]);
|
|
432
|
-
await this.call('flush', []);
|
|
433
|
-
return { state: await this.call('getState', []), sent: true };
|
|
434
|
-
}
|
|
435
|
-
case 'llui_eval_update':
|
|
436
|
-
return this.call('evalUpdate', [args.msg]);
|
|
437
|
-
case 'llui_validate_message':
|
|
438
|
-
return this.call('validateMessage', [args.msg]);
|
|
439
|
-
case 'llui_get_message_history': {
|
|
440
|
-
const opts = {};
|
|
441
|
-
if (typeof args.since === 'number')
|
|
442
|
-
opts.since = args.since;
|
|
443
|
-
if (typeof args.limit === 'number')
|
|
444
|
-
opts.limit = args.limit;
|
|
445
|
-
return this.call('getMessageHistory', [opts]);
|
|
446
|
-
}
|
|
447
|
-
case 'llui_export_trace':
|
|
448
|
-
return this.call('exportTrace', []);
|
|
449
|
-
case 'llui_get_bindings':
|
|
450
|
-
return this.call('getBindings', []);
|
|
451
|
-
case 'llui_why_did_update':
|
|
452
|
-
return this.call('whyDidUpdate', [args.bindingIndex]);
|
|
453
|
-
case 'llui_search_state':
|
|
454
|
-
return this.call('searchState', [args.query]);
|
|
455
|
-
case 'llui_clear_log':
|
|
456
|
-
await this.call('clearLog', []);
|
|
457
|
-
return { cleared: true };
|
|
458
|
-
case 'llui_list_messages':
|
|
459
|
-
return this.call('getMessageSchema', []);
|
|
460
|
-
case 'llui_decode_mask':
|
|
461
|
-
return this.call('decodeMask', [args.mask]);
|
|
462
|
-
case 'llui_mask_legend':
|
|
463
|
-
return this.call('getMaskLegend', []);
|
|
464
|
-
case 'llui_component_info':
|
|
465
|
-
return this.call('getComponentInfo', []);
|
|
466
|
-
case 'llui_describe_state':
|
|
467
|
-
return this.call('getStateSchema', []);
|
|
468
|
-
case 'llui_list_effects':
|
|
469
|
-
return this.call('getEffectSchema', []);
|
|
470
|
-
case 'llui_trace_element':
|
|
471
|
-
return this.call('getBindingsFor', [args.selector]);
|
|
472
|
-
case 'llui_snapshot_state':
|
|
473
|
-
return this.call('snapshotState', []);
|
|
474
|
-
case 'llui_restore_state':
|
|
475
|
-
await this.call('restoreState', [args.snapshot]);
|
|
476
|
-
return { restored: true, state: await this.call('getState', []) };
|
|
477
|
-
case 'llui_list_components':
|
|
478
|
-
return this.call('__listComponents', []);
|
|
479
|
-
case 'llui_select_component':
|
|
480
|
-
return this.call('__selectComponent', [args.key]);
|
|
481
|
-
case 'llui_replay_trace': {
|
|
482
|
-
const trace = (await this.call('exportTrace', []));
|
|
483
|
-
const importPath = args.importPath ?? '../src/index';
|
|
484
|
-
const exportName = args.exportName ?? trace.component;
|
|
485
|
-
return {
|
|
486
|
-
filename: `${trace.component.toLowerCase()}-replay.test.ts`,
|
|
487
|
-
code: generateReplayTest(trace, importPath, exportName),
|
|
488
|
-
entryCount: trace.entries.length,
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
case 'llui_lint': {
|
|
492
|
-
const sourceArg = args.source;
|
|
493
|
-
const pathArg = args.path;
|
|
494
|
-
const excludeArg = args.exclude;
|
|
495
|
-
if (sourceArg !== undefined && pathArg !== undefined) {
|
|
496
|
-
throw new Error("llui_lint: provide either 'source' or 'path', not both");
|
|
497
|
-
}
|
|
498
|
-
if (sourceArg === undefined && pathArg === undefined) {
|
|
499
|
-
throw new Error("llui_lint: must provide either 'source' or 'path'");
|
|
500
|
-
}
|
|
501
|
-
let code;
|
|
502
|
-
let filename;
|
|
503
|
-
if (sourceArg !== undefined) {
|
|
504
|
-
code = sourceArg;
|
|
505
|
-
filename = 'input.ts';
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
// pathArg is defined here
|
|
509
|
-
if (!pathArg.endsWith('.ts') && !pathArg.endsWith('.tsx')) {
|
|
510
|
-
throw new Error(`llui_lint: path must end in .ts or .tsx (got ${pathArg})`);
|
|
511
|
-
}
|
|
512
|
-
if (!existsSync(pathArg)) {
|
|
513
|
-
throw new Error(`llui_lint: file not found: ${pathArg}`);
|
|
514
|
-
}
|
|
515
|
-
code = readFileSync(pathArg, 'utf8');
|
|
516
|
-
filename = pathArg;
|
|
517
|
-
}
|
|
518
|
-
const result = lintIdiomatic(code, filename, {
|
|
519
|
-
exclude: excludeArg,
|
|
520
|
-
});
|
|
521
|
-
return {
|
|
522
|
-
file: filename,
|
|
523
|
-
score: result.score,
|
|
524
|
-
violations: result.violations,
|
|
525
|
-
summary: `${result.violations.length} violation(s), score ${result.score}/17`,
|
|
526
|
-
};
|
|
527
|
-
}
|
|
528
|
-
default:
|
|
529
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
530
|
-
}
|
|
124
|
+
const ctx = { relay: this.relay, cdp: null };
|
|
125
|
+
return this.registry.dispatch(name, args, ctx);
|
|
531
126
|
}
|
|
532
127
|
/** Start the MCP server on stdin/stdout */
|
|
533
128
|
start() {
|
|
@@ -603,26 +198,14 @@ export class LluiMcpServer {
|
|
|
603
198
|
}
|
|
604
199
|
}
|
|
605
200
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
import { replayTrace } from '@llui/test'
|
|
617
|
-
import { ${exportName} } from '${importPath}'
|
|
618
|
-
|
|
619
|
-
// Auto-generated from a debugging session via llui_replay_trace MCP tool.
|
|
620
|
-
// Edit the trace below to trim, reorder, or adjust expected state/effects.
|
|
621
|
-
const trace = ${traceJson} as const
|
|
622
|
-
|
|
623
|
-
it('${trace.component}: replays ${trace.entries.length} recorded message${trace.entries.length === 1 ? '' : 's'}', () => {
|
|
624
|
-
expect(() => replayTrace(${exportName}, trace as Parameters<typeof replayTrace>[1])).not.toThrow()
|
|
625
|
-
})
|
|
626
|
-
`;
|
|
627
|
-
}
|
|
201
|
+
/**
|
|
202
|
+
* Snapshot of all registered tool definitions. Kept as a named export for
|
|
203
|
+
* backward compatibility with downstream consumers that used to import the
|
|
204
|
+
* `TOOLS` array re-export under this alias.
|
|
205
|
+
*/
|
|
206
|
+
export const mcpToolDefinitions = (() => {
|
|
207
|
+
const registry = new ToolRegistry();
|
|
208
|
+
registerDebugApiTools(registry);
|
|
209
|
+
return registry.listDefinitions();
|
|
210
|
+
})();
|
|
628
211
|
//# sourceMappingURL=index.js.map
|