@llui/mcp 0.0.14 → 0.0.16
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 +60 -2
- package/dist/cli.js +202 -13
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +28 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +59 -78
- package/dist/index.js.map +1 -1
- package/dist/tools/debug-api.js +1 -1
- package/dist/tools/debug-api.js.map +1 -1
- package/dist/transports/index.d.ts +2 -2
- package/dist/transports/index.d.ts.map +1 -1
- package/dist/transports/index.js +1 -1
- package/dist/transports/index.js.map +1 -1
- package/dist/transports/relay.d.ts +57 -1
- package/dist/transports/relay.d.ts.map +1 -1
- package/dist/transports/relay.js +100 -2
- package/dist/transports/relay.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -8,14 +8,72 @@ pnpm add -D @llui/mcp
|
|
|
8
8
|
|
|
9
9
|
## Usage
|
|
10
10
|
|
|
11
|
-
The MCP server
|
|
11
|
+
The MCP server has two transports and two usage patterns.
|
|
12
|
+
|
|
13
|
+
### Plugin-launched (recommended): one-terminal dev
|
|
14
|
+
|
|
15
|
+
Install `@llui/mcp` as a dev dependency. The Vite plugin auto-detects the package and spawns `llui-mcp --http` as a child of the dev server. One `pnpm dev` starts everything; no second terminal, no stdio fuss.
|
|
12
16
|
|
|
13
17
|
```ts
|
|
14
|
-
// vite.config.ts
|
|
18
|
+
// vite.config.ts
|
|
15
19
|
import llui from '@llui/vite-plugin'
|
|
20
|
+
export default defineConfig({ plugins: [llui()] })
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Point your MCP client (e.g. Claude Code) at the HTTP endpoint. In `.mcp.json`:
|
|
24
|
+
|
|
25
|
+
```json
|
|
26
|
+
{
|
|
27
|
+
"mcpServers": {
|
|
28
|
+
"llui": {
|
|
29
|
+
"type": "http",
|
|
30
|
+
"url": "http://127.0.0.1:5200/mcp"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The MCP protocol runs on `POST /mcp`; the browser-relay WebSocket bridge shares the same port via upgrade on `/bridge`.
|
|
37
|
+
|
|
38
|
+
### Stdio (manual spawn): traditional MCP client
|
|
39
|
+
|
|
40
|
+
If your MCP client spawns servers over stdio (the older pattern), run the CLI without `--http`:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"llui": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["llui-mcp"]
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The server talks stdio to the client and stands up its own WebSocket bridge on port 5200 for the browser relay. With this pattern, set `mcpPort: 5200` explicitly in the Vite plugin so it wires to the externally-managed server instead of spawning its own:
|
|
54
|
+
|
|
55
|
+
```ts
|
|
16
56
|
export default defineConfig({ plugins: [llui({ mcpPort: 5200 })] })
|
|
17
57
|
```
|
|
18
58
|
|
|
59
|
+
### Troubleshooting: `llui-mcp doctor`
|
|
60
|
+
|
|
61
|
+
If a tool call returns a `bridge-unavailable` error or Claude simply can't talk to a running app, run the doctor to see what's wrong:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx llui-mcp doctor
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
It checks, in order:
|
|
68
|
+
|
|
69
|
+
- Is the active-marker file at `node_modules/.cache/llui-mcp/active.json` present?
|
|
70
|
+
- Is the marker JSON parseable?
|
|
71
|
+
- Has the Vite plugin stamped its `devUrl` into the marker?
|
|
72
|
+
- Is the bridge port listening on 127.0.0.1?
|
|
73
|
+
- Is the PID recorded in the marker still alive?
|
|
74
|
+
|
|
75
|
+
Each check prints `✓` or `✗` with a one-line detail. Exit code is 0 when everything passes, 1 when any check fails.
|
|
76
|
+
|
|
19
77
|
## Tools
|
|
20
78
|
|
|
21
79
|
### State Inspection
|
package/dist/cli.js
CHANGED
|
@@ -1,15 +1,204 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
server.
|
|
6
|
-
server.
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
7
|
+
import { LluiMcpServer, mcpActiveFilePath } from './index.js';
|
|
8
|
+
/**
|
|
9
|
+
* Parse `--http [port]` from argv. Returns:
|
|
10
|
+
* - null → stdio mode (default)
|
|
11
|
+
* - number → HTTP mode on that port
|
|
12
|
+
*/
|
|
13
|
+
function parseHttpFlag(argv) {
|
|
14
|
+
const idx = argv.indexOf('--http');
|
|
15
|
+
if (idx < 0)
|
|
16
|
+
return null;
|
|
17
|
+
const next = argv[idx + 1];
|
|
18
|
+
if (next && !next.startsWith('-') && /^\d+$/.test(next)) {
|
|
19
|
+
return Number(next);
|
|
20
|
+
}
|
|
21
|
+
return Number(process.env.LLUI_MCP_PORT ?? 5200);
|
|
22
|
+
}
|
|
23
|
+
const bridgePort = Number(process.env.LLUI_MCP_PORT ?? 5200);
|
|
24
|
+
const args = process.argv.slice(2);
|
|
25
|
+
const httpPort = parseHttpFlag(args);
|
|
26
|
+
if (args[0] === 'doctor') {
|
|
27
|
+
doctor(bridgePort).then((ok) => process.exit(ok ? 0 : 1), (err) => {
|
|
28
|
+
process.stderr.write(`[llui-mcp doctor] fatal: ${String(err)}\n`);
|
|
29
|
+
process.exit(2);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
main().catch((err) => {
|
|
34
|
+
process.stderr.write(`[llui-mcp] fatal: ${String(err)}\n`);
|
|
35
|
+
process.exit(1);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async function main() {
|
|
39
|
+
if (httpPort === null) {
|
|
40
|
+
// Stdio mode — Claude's `.mcp.json` spawns llui-mcp and talks over
|
|
41
|
+
// stdin/stdout. The bridge runs on its own WebSocket server on
|
|
42
|
+
// `bridgePort`.
|
|
43
|
+
const server = new LluiMcpServer(bridgePort);
|
|
44
|
+
server.startBridge();
|
|
45
|
+
const transport = new StdioServerTransport();
|
|
46
|
+
await server.connect(transport);
|
|
47
|
+
process.stderr.write(`[llui-mcp] listening on stdio; bridge ws://127.0.0.1:${bridgePort}\n`);
|
|
48
|
+
const shutdown = () => {
|
|
49
|
+
server.stopBridge();
|
|
50
|
+
process.exit(0);
|
|
51
|
+
};
|
|
52
|
+
process.on('SIGINT', shutdown);
|
|
53
|
+
process.on('SIGTERM', shutdown);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
// HTTP mode — plugin-spawned. One `http.Server` serves both the MCP
|
|
57
|
+
// Streamable HTTP transport (`/mcp`) and the browser bridge WebSocket
|
|
58
|
+
// (upgrade on `/bridge`). `.mcp.json` uses type: "http" with url
|
|
59
|
+
// `http://127.0.0.1:<port>/mcp`.
|
|
60
|
+
const mcpTransports = new Map();
|
|
61
|
+
const httpServer = createServer((req, res) => {
|
|
62
|
+
handleHttp(req, res).catch((err) => {
|
|
63
|
+
res.statusCode = 500;
|
|
64
|
+
res.setHeader('content-type', 'application/json');
|
|
65
|
+
res.end(JSON.stringify({ error: String(err) }));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
// Single bridge attached to the HTTP server; all MCP sessions share it.
|
|
69
|
+
const bridgeServer = new LluiMcpServer({ bridgePort: httpPort, attachTo: httpServer });
|
|
70
|
+
bridgeServer.startBridge();
|
|
71
|
+
httpServer.listen(httpPort, '127.0.0.1', () => {
|
|
72
|
+
process.stderr.write(`[llui-mcp] HTTP transport on http://127.0.0.1:${httpPort}/mcp; bridge ws://127.0.0.1:${httpPort}/bridge\n`);
|
|
73
|
+
});
|
|
74
|
+
const shutdown = async () => {
|
|
75
|
+
bridgeServer.stopBridge();
|
|
76
|
+
for (const t of mcpTransports.values())
|
|
77
|
+
await t.close();
|
|
78
|
+
mcpTransports.clear();
|
|
79
|
+
httpServer.close();
|
|
80
|
+
process.exit(0);
|
|
81
|
+
};
|
|
82
|
+
process.on('SIGINT', () => {
|
|
83
|
+
shutdown().catch(() => process.exit(1));
|
|
84
|
+
});
|
|
85
|
+
process.on('SIGTERM', () => {
|
|
86
|
+
shutdown().catch(() => process.exit(1));
|
|
87
|
+
});
|
|
88
|
+
async function handleHttp(req, res) {
|
|
89
|
+
const url = req.url ?? '/';
|
|
90
|
+
if (!url.startsWith('/mcp')) {
|
|
91
|
+
res.statusCode = 404;
|
|
92
|
+
res.end('not found');
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Session routing: the SDK's StreamableHTTPServerTransport is
|
|
96
|
+
// stateful. The first request (initialize) creates a session id
|
|
97
|
+
// returned in the `mcp-session-id` response header; subsequent
|
|
98
|
+
// requests carry it as the `mcp-session-id` header.
|
|
99
|
+
const sessionHeader = req.headers['mcp-session-id'];
|
|
100
|
+
const sessionId = typeof sessionHeader === 'string' ? sessionHeader : undefined;
|
|
101
|
+
let transport = sessionId ? mcpTransports.get(sessionId) : undefined;
|
|
102
|
+
if (!transport) {
|
|
103
|
+
// New session. Each MCP session gets its own server instance
|
|
104
|
+
// (SDK requirement), but all share the one browser bridge.
|
|
105
|
+
transport = new StreamableHTTPServerTransport({
|
|
106
|
+
sessionIdGenerator: () => randomUUID(),
|
|
107
|
+
onsessioninitialized: (id) => {
|
|
108
|
+
mcpTransports.set(id, transport);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
transport.onclose = () => {
|
|
112
|
+
const id = transport.sessionId;
|
|
113
|
+
if (id)
|
|
114
|
+
mcpTransports.delete(id);
|
|
115
|
+
};
|
|
116
|
+
const sessionServer = new LluiMcpServer({ bridgePort: httpPort, attachTo: httpServer });
|
|
117
|
+
await sessionServer.connect(transport);
|
|
118
|
+
}
|
|
119
|
+
await transport.handleRequest(req, res);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function doctor(port) {
|
|
123
|
+
// Offline checks only — doctor doesn't require the server to be
|
|
124
|
+
// running. Walks the same states the RelayUnavailableError diagnostic
|
|
125
|
+
// surfaces at runtime, plus a port-liveness probe.
|
|
126
|
+
const markerPath = mcpActiveFilePath();
|
|
127
|
+
const checks = [];
|
|
128
|
+
checks.push({
|
|
129
|
+
name: 'marker file',
|
|
130
|
+
ok: existsSync(markerPath),
|
|
131
|
+
detail: markerPath,
|
|
132
|
+
});
|
|
133
|
+
let markerPayload = null;
|
|
134
|
+
if (existsSync(markerPath)) {
|
|
135
|
+
try {
|
|
136
|
+
markerPayload = JSON.parse(readFileSync(markerPath, 'utf8'));
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
markerPayload = null;
|
|
140
|
+
}
|
|
141
|
+
checks.push({
|
|
142
|
+
name: 'marker valid JSON',
|
|
143
|
+
ok: markerPayload !== null,
|
|
144
|
+
detail: markerPayload !== null ? 'OK' : 'malformed — delete and restart MCP',
|
|
145
|
+
});
|
|
146
|
+
checks.push({
|
|
147
|
+
name: 'plugin devUrl stamped',
|
|
148
|
+
ok: typeof markerPayload?.devUrl === 'string',
|
|
149
|
+
detail: typeof markerPayload?.devUrl === 'string'
|
|
150
|
+
? markerPayload.devUrl
|
|
151
|
+
: 'vite-plugin has not stamped its dev URL',
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
const targetPort = markerPayload?.port ?? port;
|
|
155
|
+
const reachable = await probePort(targetPort);
|
|
156
|
+
checks.push({
|
|
157
|
+
name: `bridge port ${targetPort} listening`,
|
|
158
|
+
ok: reachable,
|
|
159
|
+
detail: reachable ? '127.0.0.1 connectable' : 'no process bound; MCP server not running',
|
|
160
|
+
});
|
|
161
|
+
if (typeof markerPayload?.pid === 'number') {
|
|
162
|
+
const alive = isPidAlive(markerPayload.pid);
|
|
163
|
+
checks.push({
|
|
164
|
+
name: `marker pid ${markerPayload.pid}`,
|
|
165
|
+
ok: alive,
|
|
166
|
+
detail: alive ? 'process alive' : 'stale — delete the marker',
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
let allOk = true;
|
|
170
|
+
process.stdout.write('llui-mcp doctor\n');
|
|
171
|
+
process.stdout.write('—\n');
|
|
172
|
+
for (const c of checks) {
|
|
173
|
+
allOk = allOk && c.ok;
|
|
174
|
+
process.stdout.write(`${c.ok ? '✓' : '✗'} ${c.name.padEnd(32)} ${c.detail}\n`);
|
|
175
|
+
}
|
|
176
|
+
process.stdout.write('—\n');
|
|
177
|
+
process.stdout.write(allOk ? 'All checks passed.\n' : 'Some checks failed — see above.\n');
|
|
178
|
+
return allOk;
|
|
179
|
+
}
|
|
180
|
+
async function probePort(port) {
|
|
181
|
+
const { Socket } = await import('node:net');
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
const sock = new Socket();
|
|
184
|
+
const done = (ok) => {
|
|
185
|
+
sock.destroy();
|
|
186
|
+
resolve(ok);
|
|
187
|
+
};
|
|
188
|
+
sock.setTimeout(500);
|
|
189
|
+
sock.on('connect', () => done(true));
|
|
190
|
+
sock.on('error', () => done(false));
|
|
191
|
+
sock.on('timeout', () => done(false));
|
|
192
|
+
sock.connect(port, '127.0.0.1');
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
function isPidAlive(pid) {
|
|
196
|
+
try {
|
|
197
|
+
process.kill(pid, 0);
|
|
198
|
+
return true;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
15
204
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,IAAI,CAAC,CAAA;AACtD,MAAM,MAAM,GAAG,IAAI,aAAa,CAAC,IAAI,CAAC,CAAA;AACtC,MAAM,CAAC,WAAW,EAAE,CAAA;AACpB,MAAM,CAAC,KAAK,EAAE,CAAA;AACd,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,wDAAwD,IAAI,IAAI,CAAC,CAAA;AAEtF,uDAAuD;AACvD,MAAM,OAAO,GAAG,GAAS,EAAE;IACzB,MAAM,CAAC,UAAU,EAAE,CAAA;IACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAA;AACD,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAA;AAC7B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\nimport { LluiMcpServer } from './index.js'\n\nconst port = Number(process.env.LLUI_MCP_PORT ?? 5200)\nconst server = new LluiMcpServer(port)\nserver.startBridge()\nserver.start()\nprocess.stderr.write(`[llui-mcp] listening on stdio; bridge ws://127.0.0.1:${port}\\n`)\n\n// Clean up the active marker file on graceful shutdown\nconst cleanup = (): void => {\n server.stopBridge()\n process.exit(0)\n}\nprocess.on('SIGINT', cleanup)\nprocess.on('SIGTERM', cleanup)\n"]}
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { createServer } from 'node:http'\nimport type { IncomingMessage, ServerResponse } from 'node:http'\nimport { randomUUID } from 'node:crypto'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js'\nimport { LluiMcpServer, mcpActiveFilePath } from './index.js'\n\n/**\n * Parse `--http [port]` from argv. Returns:\n * - null → stdio mode (default)\n * - number → HTTP mode on that port\n */\nfunction parseHttpFlag(argv: string[]): number | null {\n const idx = argv.indexOf('--http')\n if (idx < 0) return null\n const next = argv[idx + 1]\n if (next && !next.startsWith('-') && /^\\d+$/.test(next)) {\n return Number(next)\n }\n return Number(process.env.LLUI_MCP_PORT ?? 5200)\n}\n\nconst bridgePort = Number(process.env.LLUI_MCP_PORT ?? 5200)\nconst args = process.argv.slice(2)\nconst httpPort = parseHttpFlag(args)\n\nif (args[0] === 'doctor') {\n doctor(bridgePort).then(\n (ok) => process.exit(ok ? 0 : 1),\n (err) => {\n process.stderr.write(`[llui-mcp doctor] fatal: ${String(err)}\\n`)\n process.exit(2)\n },\n )\n} else {\n main().catch((err) => {\n process.stderr.write(`[llui-mcp] fatal: ${String(err)}\\n`)\n process.exit(1)\n })\n}\n\nasync function main(): Promise<void> {\n if (httpPort === null) {\n // Stdio mode — Claude's `.mcp.json` spawns llui-mcp and talks over\n // stdin/stdout. The bridge runs on its own WebSocket server on\n // `bridgePort`.\n const server = new LluiMcpServer(bridgePort)\n server.startBridge()\n const transport = new StdioServerTransport()\n await server.connect(transport)\n process.stderr.write(`[llui-mcp] listening on stdio; bridge ws://127.0.0.1:${bridgePort}\\n`)\n\n const shutdown = (): void => {\n server.stopBridge()\n process.exit(0)\n }\n process.on('SIGINT', shutdown)\n process.on('SIGTERM', shutdown)\n return\n }\n\n // HTTP mode — plugin-spawned. One `http.Server` serves both the MCP\n // Streamable HTTP transport (`/mcp`) and the browser bridge WebSocket\n // (upgrade on `/bridge`). `.mcp.json` uses type: \"http\" with url\n // `http://127.0.0.1:<port>/mcp`.\n const mcpTransports = new Map<string, StreamableHTTPServerTransport>()\n const httpServer = createServer((req, res) => {\n handleHttp(req, res).catch((err) => {\n res.statusCode = 500\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify({ error: String(err) }))\n })\n })\n\n // Single bridge attached to the HTTP server; all MCP sessions share it.\n const bridgeServer = new LluiMcpServer({ bridgePort: httpPort, attachTo: httpServer })\n bridgeServer.startBridge()\n\n httpServer.listen(httpPort, '127.0.0.1', () => {\n process.stderr.write(\n `[llui-mcp] HTTP transport on http://127.0.0.1:${httpPort}/mcp; bridge ws://127.0.0.1:${httpPort}/bridge\\n`,\n )\n })\n\n const shutdown = async (): Promise<void> => {\n bridgeServer.stopBridge()\n for (const t of mcpTransports.values()) await t.close()\n mcpTransports.clear()\n httpServer.close()\n process.exit(0)\n }\n process.on('SIGINT', () => {\n shutdown().catch(() => process.exit(1))\n })\n process.on('SIGTERM', () => {\n shutdown().catch(() => process.exit(1))\n })\n\n async function handleHttp(req: IncomingMessage, res: ServerResponse): Promise<void> {\n const url = req.url ?? '/'\n if (!url.startsWith('/mcp')) {\n res.statusCode = 404\n res.end('not found')\n return\n }\n\n // Session routing: the SDK's StreamableHTTPServerTransport is\n // stateful. The first request (initialize) creates a session id\n // returned in the `mcp-session-id` response header; subsequent\n // requests carry it as the `mcp-session-id` header.\n const sessionHeader = req.headers['mcp-session-id']\n const sessionId = typeof sessionHeader === 'string' ? sessionHeader : undefined\n let transport = sessionId ? mcpTransports.get(sessionId) : undefined\n\n if (!transport) {\n // New session. Each MCP session gets its own server instance\n // (SDK requirement), but all share the one browser bridge.\n transport = new StreamableHTTPServerTransport({\n sessionIdGenerator: () => randomUUID(),\n onsessioninitialized: (id: string) => {\n mcpTransports.set(id, transport!)\n },\n })\n transport.onclose = () => {\n const id = transport!.sessionId\n if (id) mcpTransports.delete(id)\n }\n const sessionServer = new LluiMcpServer({ bridgePort: httpPort!, attachTo: httpServer })\n await sessionServer.connect(transport)\n }\n\n await transport.handleRequest(req, res)\n }\n}\n\nasync function doctor(port: number): Promise<boolean> {\n // Offline checks only — doctor doesn't require the server to be\n // running. Walks the same states the RelayUnavailableError diagnostic\n // surfaces at runtime, plus a port-liveness probe.\n const markerPath = mcpActiveFilePath()\n const checks: Array<{ name: string; ok: boolean; detail: string }> = []\n\n checks.push({\n name: 'marker file',\n ok: existsSync(markerPath),\n detail: markerPath,\n })\n\n let markerPayload: { port?: number; pid?: number; devUrl?: string } | null = null\n if (existsSync(markerPath)) {\n try {\n markerPayload = JSON.parse(readFileSync(markerPath, 'utf8')) as {\n port?: number\n pid?: number\n devUrl?: string\n }\n } catch {\n markerPayload = null\n }\n checks.push({\n name: 'marker valid JSON',\n ok: markerPayload !== null,\n detail: markerPayload !== null ? 'OK' : 'malformed — delete and restart MCP',\n })\n checks.push({\n name: 'plugin devUrl stamped',\n ok: typeof markerPayload?.devUrl === 'string',\n detail:\n typeof markerPayload?.devUrl === 'string'\n ? markerPayload.devUrl\n : 'vite-plugin has not stamped its dev URL',\n })\n }\n\n const targetPort = markerPayload?.port ?? port\n const reachable = await probePort(targetPort)\n checks.push({\n name: `bridge port ${targetPort} listening`,\n ok: reachable,\n detail: reachable ? '127.0.0.1 connectable' : 'no process bound; MCP server not running',\n })\n\n if (typeof markerPayload?.pid === 'number') {\n const alive = isPidAlive(markerPayload.pid)\n checks.push({\n name: `marker pid ${markerPayload.pid}`,\n ok: alive,\n detail: alive ? 'process alive' : 'stale — delete the marker',\n })\n }\n\n let allOk = true\n process.stdout.write('llui-mcp doctor\\n')\n process.stdout.write('—\\n')\n for (const c of checks) {\n allOk = allOk && c.ok\n process.stdout.write(`${c.ok ? '✓' : '✗'} ${c.name.padEnd(32)} ${c.detail}\\n`)\n }\n process.stdout.write('—\\n')\n process.stdout.write(allOk ? 'All checks passed.\\n' : 'Some checks failed — see above.\\n')\n return allOk\n}\n\nasync function probePort(port: number): Promise<boolean> {\n const { Socket } = await import('node:net')\n return new Promise<boolean>((resolve) => {\n const sock = new Socket()\n const done = (ok: boolean): void => {\n sock.destroy()\n resolve(ok)\n }\n sock.setTimeout(500)\n sock.on('connect', () => done(true))\n sock.on('error', () => done(false))\n sock.on('timeout', () => done(false))\n sock.connect(port, '127.0.0.1')\n })\n}\n\nfunction isPidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { LluiDebugAPI } from '@llui/dom';
|
|
2
|
+
import type { Server as HttpServer } from 'node:http';
|
|
3
|
+
import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
|
|
2
4
|
import { type ToolDefinition } from './tool-registry.js';
|
|
3
5
|
/**
|
|
4
6
|
* Walk up from `start` until we find a workspace root marker. Used by
|
|
@@ -23,12 +25,37 @@ export declare function findWorkspaceRoot(start?: string): string;
|
|
|
23
25
|
* when one runs from the repo root and the other from a subpackage.
|
|
24
26
|
*/
|
|
25
27
|
export declare function mcpActiveFilePath(cwd?: string): string;
|
|
28
|
+
export interface LluiMcpServerOptions {
|
|
29
|
+
/**
|
|
30
|
+
* Port for the browser-relay WebSocket bridge. When the MCP transport
|
|
31
|
+
* is stdio (the CLI default), the relay stands up its own server on
|
|
32
|
+
* this port. When the MCP transport is HTTP, the relay attaches to
|
|
33
|
+
* that HTTP server and the MCP protocol + bridge share a single port.
|
|
34
|
+
*/
|
|
35
|
+
bridgePort?: number;
|
|
36
|
+
/**
|
|
37
|
+
* Optional pre-existing `http.Server` to share with the bridge. When
|
|
38
|
+
* provided, the bridge attaches to it via upgrade routing on
|
|
39
|
+
* `/bridge`; `bridgePort` is ignored for server-creation purposes
|
|
40
|
+
* (but still written into the marker file so consumers know where to
|
|
41
|
+
* connect).
|
|
42
|
+
*/
|
|
43
|
+
attachTo?: HttpServer;
|
|
44
|
+
}
|
|
26
45
|
export declare class LluiMcpServer {
|
|
27
46
|
private readonly registry;
|
|
28
47
|
private readonly relay;
|
|
29
48
|
private readonly bridgePort;
|
|
49
|
+
private readonly mcp;
|
|
30
50
|
private devUrl;
|
|
31
|
-
constructor(
|
|
51
|
+
constructor(optsOrPort?: LluiMcpServerOptions | number);
|
|
52
|
+
private registerMcpHandlers;
|
|
53
|
+
/**
|
|
54
|
+
* Connect the SDK MCP server to a transport (stdio, HTTP, etc).
|
|
55
|
+
* The CLI builds the transport based on command-line flags and
|
|
56
|
+
* hands it in here.
|
|
57
|
+
*/
|
|
58
|
+
connect(transport: Transport): Promise<void>;
|
|
32
59
|
/** Connect to a debug API instance directly (for in-process usage). */
|
|
33
60
|
connectDirect(api: LluiDebugAPI): void;
|
|
34
61
|
/**
|
|
@@ -51,9 +78,6 @@ export declare class LluiMcpServer {
|
|
|
51
78
|
getTools(): ToolDefinition[];
|
|
52
79
|
/** Handle an MCP tool call */
|
|
53
80
|
handleToolCall(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
54
|
-
/** Start the MCP server on stdin/stdout */
|
|
55
|
-
start(): void;
|
|
56
|
-
private handleRequest;
|
|
57
81
|
}
|
|
58
82
|
/**
|
|
59
83
|
* Snapshot of all registered tool definitions. Kept as a named export for
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAG7C,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIxF;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,CAgBvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,CAErE;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAG7C,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAA;AAE9E,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAIxF;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,GAAE,MAAsB,GAAG,MAAM,CAgBvE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,CAErE;AAID,MAAM,WAAW,oBAAoB;IACnC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,UAAU,CAAA;CACtB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAc;IACvC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAC/C,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAW;IAC/B,OAAO,CAAC,MAAM,CAAsB;gBAExB,UAAU,GAAE,oBAAoB,GAAG,MAAa;IAqB5D,OAAO,CAAC,mBAAmB;IAoC3B;;;;OAIG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,uEAAuE;IACvE,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAItC;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAK5B;;;;OAIG;IACH,WAAW,IAAI,IAAI;IAQnB,UAAU,IAAI,IAAI;IAKlB,OAAO,CAAC,eAAe;IAevB,OAAO,CAAC,gBAAgB;IASxB,6CAA6C;IAC7C,QAAQ,IAAI,cAAc,EAAE;IAI5B,8BAA8B;IACxB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC;CAIpF;AAED;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,cAAc,EAI3C,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs';
|
|
2
2
|
import { dirname, resolve } from 'node:path';
|
|
3
|
+
import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
4
|
+
import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
|
|
3
5
|
import { ToolRegistry } from './tool-registry.js';
|
|
4
6
|
import { registerDebugApiTools } from './tools/index.js';
|
|
5
|
-
import { WebSocketRelayTransport } from './transports/index.js';
|
|
7
|
+
import { WebSocketRelayTransport, RelayUnavailableError } from './transports/index.js';
|
|
6
8
|
/**
|
|
7
9
|
* Walk up from `start` until we find a workspace root marker. Used by
|
|
8
10
|
* both the MCP server (writing the active marker) and the Vite plugin
|
|
@@ -47,17 +49,69 @@ export function findWorkspaceRoot(start = process.cwd()) {
|
|
|
47
49
|
export function mcpActiveFilePath(cwd = process.cwd()) {
|
|
48
50
|
return resolve(findWorkspaceRoot(cwd), 'node_modules/.cache/llui-mcp/active.json');
|
|
49
51
|
}
|
|
50
|
-
// ── MCP Server ──────────────────────────────────────────────────
|
|
51
52
|
export class LluiMcpServer {
|
|
52
53
|
registry;
|
|
53
54
|
relay;
|
|
54
55
|
bridgePort;
|
|
56
|
+
mcp;
|
|
55
57
|
devUrl = null;
|
|
56
|
-
constructor(
|
|
57
|
-
|
|
58
|
+
constructor(optsOrPort = 5200) {
|
|
59
|
+
const opts = typeof optsOrPort === 'number' ? { bridgePort: optsOrPort } : optsOrPort;
|
|
60
|
+
this.bridgePort = opts.bridgePort ?? 5200;
|
|
58
61
|
this.registry = new ToolRegistry();
|
|
59
|
-
this.relay = new WebSocketRelayTransport({
|
|
62
|
+
this.relay = new WebSocketRelayTransport({
|
|
63
|
+
port: opts.attachTo ? undefined : this.bridgePort,
|
|
64
|
+
attachTo: opts.attachTo,
|
|
65
|
+
markerPath: mcpActiveFilePath(),
|
|
66
|
+
});
|
|
60
67
|
registerDebugApiTools(this.registry);
|
|
68
|
+
// SDK-managed MCP server — owns the JSON-RPC protocol, handshake,
|
|
69
|
+
// session lifecycle. Transport is plugged in later via `connect()`.
|
|
70
|
+
this.mcp = new McpServer({ name: '@llui/mcp', version: '0.0.15' }, { capabilities: { tools: {} } });
|
|
71
|
+
this.registerMcpHandlers();
|
|
72
|
+
}
|
|
73
|
+
registerMcpHandlers() {
|
|
74
|
+
this.mcp.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
75
|
+
tools: this.getTools().map((t) => ({
|
|
76
|
+
name: t.name,
|
|
77
|
+
description: t.description,
|
|
78
|
+
inputSchema: t.inputSchema,
|
|
79
|
+
})),
|
|
80
|
+
}));
|
|
81
|
+
this.mcp.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
82
|
+
const { name, arguments: args } = request.params;
|
|
83
|
+
try {
|
|
84
|
+
const result = await this.handleToolCall(name, args ?? {});
|
|
85
|
+
return {
|
|
86
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
// Bridge-unavailable errors carry a structured diagnostic — surface
|
|
91
|
+
// it as an isError tool result so the caller (typically Claude) sees
|
|
92
|
+
// WHY the browser isn't reachable, not just that it failed.
|
|
93
|
+
if (err instanceof RelayUnavailableError) {
|
|
94
|
+
return {
|
|
95
|
+
isError: true,
|
|
96
|
+
content: [
|
|
97
|
+
{
|
|
98
|
+
type: 'text',
|
|
99
|
+
text: JSON.stringify({ error: 'bridge-unavailable', ...err.diagnostic }, null, 2),
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Connect the SDK MCP server to a transport (stdio, HTTP, etc).
|
|
110
|
+
* The CLI builds the transport based on command-line flags and
|
|
111
|
+
* hands it in here.
|
|
112
|
+
*/
|
|
113
|
+
async connect(transport) {
|
|
114
|
+
await this.mcp.connect(transport);
|
|
61
115
|
}
|
|
62
116
|
/** Connect to a debug API instance directly (for in-process usage). */
|
|
63
117
|
connectDirect(api) {
|
|
@@ -124,79 +178,6 @@ export class LluiMcpServer {
|
|
|
124
178
|
const ctx = { relay: this.relay, cdp: null };
|
|
125
179
|
return this.registry.dispatch(name, args, ctx);
|
|
126
180
|
}
|
|
127
|
-
/** Start the MCP server on stdin/stdout */
|
|
128
|
-
start() {
|
|
129
|
-
let buffer = '';
|
|
130
|
-
process.stdin.setEncoding('utf8');
|
|
131
|
-
process.stdin.on('data', (chunk) => {
|
|
132
|
-
buffer += chunk;
|
|
133
|
-
// MCP uses newline-delimited JSON
|
|
134
|
-
const lines = buffer.split('\n');
|
|
135
|
-
buffer = lines.pop(); // keep incomplete line
|
|
136
|
-
for (const line of lines) {
|
|
137
|
-
if (!line.trim())
|
|
138
|
-
continue;
|
|
139
|
-
try {
|
|
140
|
-
const request = JSON.parse(line);
|
|
141
|
-
this.handleRequest(request).then((response) => {
|
|
142
|
-
process.stdout.write(JSON.stringify(response) + '\n');
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
// Ignore parse errors
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
async handleRequest(request) {
|
|
152
|
-
try {
|
|
153
|
-
switch (request.method) {
|
|
154
|
-
case 'initialize':
|
|
155
|
-
return {
|
|
156
|
-
jsonrpc: '2.0',
|
|
157
|
-
id: request.id,
|
|
158
|
-
result: {
|
|
159
|
-
protocolVersion: '2024-11-05',
|
|
160
|
-
capabilities: { tools: {} },
|
|
161
|
-
serverInfo: { name: '@llui/mcp', version: '0.0.0' },
|
|
162
|
-
},
|
|
163
|
-
};
|
|
164
|
-
case 'tools/list':
|
|
165
|
-
return {
|
|
166
|
-
jsonrpc: '2.0',
|
|
167
|
-
id: request.id,
|
|
168
|
-
result: { tools: this.getTools() },
|
|
169
|
-
};
|
|
170
|
-
case 'tools/call': {
|
|
171
|
-
const params = request.params;
|
|
172
|
-
const result = await this.handleToolCall(params.name, params.arguments ?? {});
|
|
173
|
-
return {
|
|
174
|
-
jsonrpc: '2.0',
|
|
175
|
-
id: request.id,
|
|
176
|
-
result: {
|
|
177
|
-
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
178
|
-
},
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
default:
|
|
182
|
-
return {
|
|
183
|
-
jsonrpc: '2.0',
|
|
184
|
-
id: request.id,
|
|
185
|
-
error: {
|
|
186
|
-
code: -32601,
|
|
187
|
-
message: `Method not found: ${request.method}`,
|
|
188
|
-
},
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
catch (err) {
|
|
193
|
-
return {
|
|
194
|
-
jsonrpc: '2.0',
|
|
195
|
-
id: request.id,
|
|
196
|
-
error: { code: -32000, message: String(err) },
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
181
|
}
|
|
201
182
|
/**
|
|
202
183
|
* Snapshot of all registered tool definitions. Kept as a named export for
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LluiDebugAPI } from '@llui/dom'\nimport { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { ToolRegistry, type ToolContext, type ToolDefinition } from './tool-registry.js'\nimport { registerDebugApiTools } from './tools/index.js'\nimport { WebSocketRelayTransport } from './transports/index.js'\n\n/**\n * Walk up from `start` until we find a workspace root marker. Used by\n * both the MCP server (writing the active marker) and the Vite plugin\n * (watching it) so they agree on a single shared location regardless of\n * which subdirectory each process happens to be running in.\n *\n * Strong markers (workspace root): pnpm-workspace.yaml, .git directory.\n * If neither is found anywhere up the chain, falls back to the highest\n * package.json above `start`. For pnpm monorepos this finds the workspace\n * root from any subpackage; for single-package projects it finds the\n * package root.\n */\nexport function findWorkspaceRoot(start: string = process.cwd()): string {\n let dir = resolve(start)\n let lastPackageJson: string | null = null\n while (true) {\n // Strong markers — return immediately\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir\n if (existsSync(resolve(dir, '.git'))) return dir\n // Track the highest package.json as a fallback\n if (existsSync(resolve(dir, 'package.json'))) lastPackageJson = dir\n const parent = dirname(dir)\n if (parent === dir) {\n // Reached filesystem root — return the highest package.json we saw\n return lastPackageJson ?? start\n }\n dir = parent\n }\n}\n\n/**\n * Path where the MCP server writes its active port marker. Vite plugins\n * watch this file to auto-trigger browser-side `__lluiConnect()` whenever\n * the MCP server starts, regardless of whether Vite or MCP started first.\n *\n * Resolved relative to the workspace root (not the immediate cwd) so the\n * MCP server and the Vite plugin always agree on a single location even\n * when one runs from the repo root and the other from a subpackage.\n */\nexport function mcpActiveFilePath(cwd: string = process.cwd()): string {\n return resolve(findWorkspaceRoot(cwd), 'node_modules/.cache/llui-mcp/active.json')\n}\n\n// ── MCP Protocol Types ──────────────────────────────────────────\n\ninterface JsonRpcRequest {\n jsonrpc: '2.0'\n id: string | number\n method: string\n params?: Record<string, unknown>\n}\n\ninterface JsonRpcResponse {\n jsonrpc: '2.0'\n id: string | number\n result?: unknown\n error?: { code: number; message: string; data?: unknown }\n}\n\n// ── MCP Server ──────────────────────────────────────────────────\n\nexport class LluiMcpServer {\n private readonly registry: ToolRegistry\n private readonly relay: WebSocketRelayTransport\n private readonly bridgePort: number\n private devUrl: string | null = null\n\n constructor(bridgePort = 5200) {\n this.bridgePort = bridgePort\n this.registry = new ToolRegistry()\n this.relay = new WebSocketRelayTransport({ port: bridgePort })\n registerDebugApiTools(this.registry)\n }\n\n /** Connect to a debug API instance directly (for in-process usage). */\n connectDirect(api: LluiDebugAPI): void {\n this.relay.connectDirect(api)\n }\n\n /**\n * Set the dev-server URL that Phase 2's CDP fallback can navigate a\n * Playwright browser to. Persisted into the active marker file so the\n * Vite plugin (or other consumers) can rebroadcast it. If the bridge is\n * already running, rewrites the marker so consumers see the update.\n */\n setDevUrl(url: string): void {\n this.devUrl = url\n if (this.relay.isServerRunning()) this.writeActiveFile()\n }\n\n /**\n * Start a WebSocket server on the configured bridge port. The browser-side\n * relay (injected by the Vite plugin in dev mode) connects here and forwards\n * debug-API calls.\n */\n startBridge(): void {\n this.relay.start()\n\n // Write the active marker file so Vite plugins watching it can\n // dispatch an HMR custom event to auto-trigger browser connects.\n this.writeActiveFile()\n }\n\n stopBridge(): void {\n this.relay.stop()\n this.removeActiveFile()\n }\n\n private writeActiveFile(): void {\n try {\n const path = mcpActiveFilePath()\n mkdirSync(dirname(path), { recursive: true })\n const payload: { port: number; pid: number; devUrl?: string } = {\n port: this.bridgePort,\n pid: process.pid,\n }\n if (this.devUrl !== null) payload.devUrl = this.devUrl\n writeFileSync(path, JSON.stringify(payload))\n } catch {\n // Best-effort — failure to write the marker should not crash the server\n }\n }\n\n private removeActiveFile(): void {\n try {\n const path = mcpActiveFilePath()\n if (existsSync(path)) unlinkSync(path)\n } catch {\n // Ignore — file may already be gone\n }\n }\n\n /** Get tool definitions for MCP handshake */\n getTools(): ToolDefinition[] {\n return this.registry.listDefinitions()\n }\n\n /** Handle an MCP tool call */\n async handleToolCall(name: string, args: Record<string, unknown>): Promise<unknown> {\n const ctx: ToolContext = { relay: this.relay, cdp: null }\n return this.registry.dispatch(name, args, ctx)\n }\n\n /** Start the MCP server on stdin/stdout */\n start(): void {\n let buffer = ''\n\n process.stdin.setEncoding('utf8')\n process.stdin.on('data', (chunk: string) => {\n buffer += chunk\n // MCP uses newline-delimited JSON\n const lines = buffer.split('\\n')\n buffer = lines.pop()! // keep incomplete line\n for (const line of lines) {\n if (!line.trim()) continue\n try {\n const request = JSON.parse(line) as JsonRpcRequest\n this.handleRequest(request).then((response) => {\n process.stdout.write(JSON.stringify(response) + '\\n')\n })\n } catch {\n // Ignore parse errors\n }\n }\n })\n }\n\n private async handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse> {\n try {\n switch (request.method) {\n case 'initialize':\n return {\n jsonrpc: '2.0',\n id: request.id,\n result: {\n protocolVersion: '2024-11-05',\n capabilities: { tools: {} },\n serverInfo: { name: '@llui/mcp', version: '0.0.0' },\n },\n }\n\n case 'tools/list':\n return {\n jsonrpc: '2.0',\n id: request.id,\n result: { tools: this.getTools() },\n }\n\n case 'tools/call': {\n const params = request.params as {\n name: string\n arguments: Record<string, unknown>\n }\n const result = await this.handleToolCall(params.name, params.arguments ?? {})\n return {\n jsonrpc: '2.0',\n id: request.id,\n result: {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n },\n }\n }\n\n default:\n return {\n jsonrpc: '2.0',\n id: request.id,\n error: {\n code: -32601,\n message: `Method not found: ${request.method}`,\n },\n }\n }\n } catch (err) {\n return {\n jsonrpc: '2.0',\n id: request.id,\n error: { code: -32000, message: String(err) },\n }\n }\n }\n}\n\n/**\n * Snapshot of all registered tool definitions. Kept as a named export for\n * backward compatibility with downstream consumers that used to import the\n * `TOOLS` array re-export under this alias.\n */\nexport const mcpToolDefinitions: ToolDefinition[] = (() => {\n const registry = new ToolRegistry()\n registerDebugApiTools(registry)\n return registry.listDefinitions()\n})()\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import type { LluiDebugAPI } from '@llui/dom'\nimport { mkdirSync, writeFileSync, unlinkSync, existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport type { Server as HttpServer } from 'node:http'\nimport { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js'\nimport type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'\nimport { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'\nimport { ToolRegistry, type ToolContext, type ToolDefinition } from './tool-registry.js'\nimport { registerDebugApiTools } from './tools/index.js'\nimport { WebSocketRelayTransport, RelayUnavailableError } from './transports/index.js'\n\n/**\n * Walk up from `start` until we find a workspace root marker. Used by\n * both the MCP server (writing the active marker) and the Vite plugin\n * (watching it) so they agree on a single shared location regardless of\n * which subdirectory each process happens to be running in.\n *\n * Strong markers (workspace root): pnpm-workspace.yaml, .git directory.\n * If neither is found anywhere up the chain, falls back to the highest\n * package.json above `start`. For pnpm monorepos this finds the workspace\n * root from any subpackage; for single-package projects it finds the\n * package root.\n */\nexport function findWorkspaceRoot(start: string = process.cwd()): string {\n let dir = resolve(start)\n let lastPackageJson: string | null = null\n while (true) {\n // Strong markers — return immediately\n if (existsSync(resolve(dir, 'pnpm-workspace.yaml'))) return dir\n if (existsSync(resolve(dir, '.git'))) return dir\n // Track the highest package.json as a fallback\n if (existsSync(resolve(dir, 'package.json'))) lastPackageJson = dir\n const parent = dirname(dir)\n if (parent === dir) {\n // Reached filesystem root — return the highest package.json we saw\n return lastPackageJson ?? start\n }\n dir = parent\n }\n}\n\n/**\n * Path where the MCP server writes its active port marker. Vite plugins\n * watch this file to auto-trigger browser-side `__lluiConnect()` whenever\n * the MCP server starts, regardless of whether Vite or MCP started first.\n *\n * Resolved relative to the workspace root (not the immediate cwd) so the\n * MCP server and the Vite plugin always agree on a single location even\n * when one runs from the repo root and the other from a subpackage.\n */\nexport function mcpActiveFilePath(cwd: string = process.cwd()): string {\n return resolve(findWorkspaceRoot(cwd), 'node_modules/.cache/llui-mcp/active.json')\n}\n\n// ── MCP Server ──────────────────────────────────────────────────\n\nexport interface LluiMcpServerOptions {\n /**\n * Port for the browser-relay WebSocket bridge. When the MCP transport\n * is stdio (the CLI default), the relay stands up its own server on\n * this port. When the MCP transport is HTTP, the relay attaches to\n * that HTTP server and the MCP protocol + bridge share a single port.\n */\n bridgePort?: number\n /**\n * Optional pre-existing `http.Server` to share with the bridge. When\n * provided, the bridge attaches to it via upgrade routing on\n * `/bridge`; `bridgePort` is ignored for server-creation purposes\n * (but still written into the marker file so consumers know where to\n * connect).\n */\n attachTo?: HttpServer\n}\n\nexport class LluiMcpServer {\n private readonly registry: ToolRegistry\n private readonly relay: WebSocketRelayTransport\n private readonly bridgePort: number\n private readonly mcp: McpServer\n private devUrl: string | null = null\n\n constructor(optsOrPort: LluiMcpServerOptions | number = 5200) {\n const opts: LluiMcpServerOptions =\n typeof optsOrPort === 'number' ? { bridgePort: optsOrPort } : optsOrPort\n this.bridgePort = opts.bridgePort ?? 5200\n this.registry = new ToolRegistry()\n this.relay = new WebSocketRelayTransport({\n port: opts.attachTo ? undefined : this.bridgePort,\n attachTo: opts.attachTo,\n markerPath: mcpActiveFilePath(),\n })\n registerDebugApiTools(this.registry)\n\n // SDK-managed MCP server — owns the JSON-RPC protocol, handshake,\n // session lifecycle. Transport is plugged in later via `connect()`.\n this.mcp = new McpServer(\n { name: '@llui/mcp', version: '0.0.15' },\n { capabilities: { tools: {} } },\n )\n this.registerMcpHandlers()\n }\n\n private registerMcpHandlers(): void {\n this.mcp.setRequestHandler(ListToolsRequestSchema, async () => ({\n tools: this.getTools().map((t) => ({\n name: t.name,\n description: t.description,\n inputSchema: t.inputSchema,\n })),\n }))\n\n this.mcp.setRequestHandler(CallToolRequestSchema, async (request) => {\n const { name, arguments: args } = request.params\n try {\n const result = await this.handleToolCall(name, args ?? {})\n return {\n content: [{ type: 'text' as const, text: JSON.stringify(result, null, 2) }],\n }\n } catch (err) {\n // Bridge-unavailable errors carry a structured diagnostic — surface\n // it as an isError tool result so the caller (typically Claude) sees\n // WHY the browser isn't reachable, not just that it failed.\n if (err instanceof RelayUnavailableError) {\n return {\n isError: true,\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({ error: 'bridge-unavailable', ...err.diagnostic }, null, 2),\n },\n ],\n }\n }\n throw err\n }\n })\n }\n\n /**\n * Connect the SDK MCP server to a transport (stdio, HTTP, etc).\n * The CLI builds the transport based on command-line flags and\n * hands it in here.\n */\n async connect(transport: Transport): Promise<void> {\n await this.mcp.connect(transport)\n }\n\n /** Connect to a debug API instance directly (for in-process usage). */\n connectDirect(api: LluiDebugAPI): void {\n this.relay.connectDirect(api)\n }\n\n /**\n * Set the dev-server URL that Phase 2's CDP fallback can navigate a\n * Playwright browser to. Persisted into the active marker file so the\n * Vite plugin (or other consumers) can rebroadcast it. If the bridge is\n * already running, rewrites the marker so consumers see the update.\n */\n setDevUrl(url: string): void {\n this.devUrl = url\n if (this.relay.isServerRunning()) this.writeActiveFile()\n }\n\n /**\n * Start a WebSocket server on the configured bridge port. The browser-side\n * relay (injected by the Vite plugin in dev mode) connects here and forwards\n * debug-API calls.\n */\n startBridge(): void {\n this.relay.start()\n\n // Write the active marker file so Vite plugins watching it can\n // dispatch an HMR custom event to auto-trigger browser connects.\n this.writeActiveFile()\n }\n\n stopBridge(): void {\n this.relay.stop()\n this.removeActiveFile()\n }\n\n private writeActiveFile(): void {\n try {\n const path = mcpActiveFilePath()\n mkdirSync(dirname(path), { recursive: true })\n const payload: { port: number; pid: number; devUrl?: string } = {\n port: this.bridgePort,\n pid: process.pid,\n }\n if (this.devUrl !== null) payload.devUrl = this.devUrl\n writeFileSync(path, JSON.stringify(payload))\n } catch {\n // Best-effort — failure to write the marker should not crash the server\n }\n }\n\n private removeActiveFile(): void {\n try {\n const path = mcpActiveFilePath()\n if (existsSync(path)) unlinkSync(path)\n } catch {\n // Ignore — file may already be gone\n }\n }\n\n /** Get tool definitions for MCP handshake */\n getTools(): ToolDefinition[] {\n return this.registry.listDefinitions()\n }\n\n /** Handle an MCP tool call */\n async handleToolCall(name: string, args: Record<string, unknown>): Promise<unknown> {\n const ctx: ToolContext = { relay: this.relay, cdp: null }\n return this.registry.dispatch(name, args, ctx)\n }\n}\n\n/**\n * Snapshot of all registered tool definitions. Kept as a named export for\n * backward compatibility with downstream consumers that used to import the\n * `TOOLS` array re-export under this alias.\n */\nexport const mcpToolDefinitions: ToolDefinition[] = (() => {\n const registry = new ToolRegistry()\n registerDebugApiTools(registry)\n return registry.listDefinitions()\n})()\n"]}
|
package/dist/tools/debug-api.js
CHANGED
|
@@ -414,7 +414,7 @@ export function registerDebugApiTools(registry) {
|
|
|
414
414
|
}, 'debug-api', async (args, ctx) => ctx.relay.call('getEachDiff', [args.sinceIndex]));
|
|
415
415
|
registry.register({
|
|
416
416
|
name: 'llui_scope_tree',
|
|
417
|
-
description: "Walk the scope tree starting at the component root (or a specific scopeId). Returns a
|
|
417
|
+
description: "Walk the scope tree starting at the component root (or a specific scopeId). Returns a LifetimeNode tree with kind (root/show/each/branch/child/portal/foreign) and children. Pass 'depth' to limit traversal, 'scopeId' to start elsewhere.",
|
|
418
418
|
inputSchema: {
|
|
419
419
|
type: 'object',
|
|
420
420
|
properties: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug-api.js","sourceRoot":"","sources":["../../src/tools/debug-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { lintIdiomatic } from '@llui/lint-idiomatic'\nimport type { ToolRegistry } from '../tool-registry.js'\nimport { generateReplayTest } from './replay-test-generator.js'\nimport { domDiff, diffState } from '../util/diff.js'\n\n/**\n * Register the 23 debug-API-backed tools. Every handler here routes through\n * `ctx.relay!.call(method, args)` — which transparently dispatches to either\n * an in-process `LluiDebugAPI` or the WebSocket bridge, depending on how the\n * transport was wired.\n */\nexport function registerDebugApiTools(registry: ToolRegistry): void {\n registry.register(\n {\n name: 'llui_get_state',\n description:\n 'Get the current state of the LLui component. Returns a JSON-serializable state object.',\n inputSchema: {\n type: 'object',\n properties: {\n component: {\n type: 'string',\n description: 'Component name (defaults to root)',\n },\n },\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getState', []),\n )\n\n registry.register(\n {\n name: 'llui_send_message',\n description:\n 'Send a message to the component and return the new state and effects. Validates the message first. Calls flush() automatically.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: {\n type: 'object',\n description: 'The message to send (must be a valid Msg variant)',\n },\n },\n required: ['msg'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const errors = (await ctx.relay!.call('validateMessage', [args.msg])) as unknown[] | null\n if (errors) return { errors, sent: false }\n await ctx.relay!.call('send', [args.msg])\n await ctx.relay!.call('flush', [])\n return { state: await ctx.relay!.call('getState', []), sent: true }\n },\n )\n\n registry.register(\n {\n name: 'llui_eval_update',\n description:\n 'Dry-run: call update(state, msg) without applying. Returns what the new state and effects would be without modifying the running app.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: {\n type: 'object',\n description: 'The hypothetical message to evaluate',\n },\n },\n required: ['msg'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('evalUpdate', [args.msg]),\n )\n\n registry.register(\n {\n name: 'llui_validate_message',\n description:\n 'Validate a message against the component Msg type. Returns errors or null if valid.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: {\n type: 'object',\n description: 'The message to validate',\n },\n },\n required: ['msg'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('validateMessage', [args.msg]),\n )\n\n registry.register(\n {\n name: 'llui_get_message_history',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n since: {\n type: 'number',\n description: 'Return entries with index strictly greater than this.',\n },\n limit: {\n type: 'number',\n description: 'Max entries to return (the N most recent).',\n },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const opts: { since?: number; limit?: number } = {}\n if (typeof args.since === 'number') opts.since = args.since\n if (typeof args.limit === 'number') opts.limit = args.limit\n return ctx.relay!.call('getMessageHistory', [opts])\n },\n )\n\n registry.register(\n {\n name: 'llui_export_trace',\n description: 'Export the current session as a replayable LluiTrace JSON.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('exportTrace', []),\n )\n\n registry.register(\n {\n name: 'llui_get_bindings',\n description:\n 'Get all active reactive bindings with their masks, last values, and DOM targets.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'string',\n description: 'Filter by DOM selector or mask value',\n },\n },\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getBindings', []),\n )\n\n registry.register(\n {\n name: 'llui_why_did_update',\n description:\n 'Explain why a specific binding re-evaluated: which mask bits were dirty, what the accessor returned, what the previous value was.',\n inputSchema: {\n type: 'object',\n properties: {\n bindingIndex: {\n type: 'number',\n description: 'The binding index to inspect',\n },\n },\n required: ['bindingIndex'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('whyDidUpdate', [args.bindingIndex as number]),\n )\n\n registry.register(\n {\n name: 'llui_search_state',\n description:\n 'Search current state using a dot-separated path query. E.g., \"cart.items\" returns the items array.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Dot-separated path to search. E.g., \"user.name\", \"items\"',\n },\n },\n required: ['query'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('searchState', [args.query as string]),\n )\n\n registry.register(\n {\n name: 'llui_clear_log',\n description: 'Clear the message and effects history.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => {\n await ctx.relay!.call('clearLog', [])\n return { cleared: true }\n },\n )\n\n registry.register(\n {\n name: 'llui_list_messages',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getMessageSchema', []),\n )\n\n registry.register(\n {\n name: 'llui_decode_mask',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {\n mask: { type: 'number', description: 'The dirtyMask value to decode' },\n },\n required: ['mask'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('decodeMask', [args.mask as number]),\n )\n\n registry.register(\n {\n name: 'llui_mask_legend',\n description:\n 'Return the compiler-generated bit→field map for this component. Example: { todos: 1, filter: 2, nextId: 4 } means bit 0 represents `todos`, etc.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getMaskLegend', []),\n )\n\n registry.register(\n {\n name: 'llui_component_info',\n description:\n 'Get component name and source location (file + line) of the component() declaration. Lets you find where to read or edit the component.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getComponentInfo', []),\n )\n\n registry.register(\n {\n name: 'llui_describe_state',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getStateSchema', []),\n )\n\n registry.register(\n {\n name: 'llui_list_effects',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getEffectSchema', []),\n )\n\n registry.register(\n {\n name: 'llui_trace_element',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {\n selector: {\n type: 'string',\n description: 'CSS selector (e.g. `.todo.active`, `#submit`)',\n },\n },\n required: ['selector'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getBindingsFor', [args.selector as string]),\n )\n\n registry.register(\n {\n name: 'llui_snapshot_state',\n description:\n '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.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('snapshotState', []),\n )\n\n registry.register(\n {\n name: 'llui_restore_state',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n snapshot: {\n description: 'The state object returned by llui_snapshot_state.',\n },\n },\n required: ['snapshot'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n await ctx.relay!.call('restoreState', [args.snapshot])\n return { restored: true, state: await ctx.relay!.call('getState', []) }\n },\n )\n\n registry.register(\n {\n name: 'llui_list_components',\n description:\n 'List all currently-mounted LLui components + which one is active (being targeted by subsequent tool calls). Multi-mount apps show one entry per mount.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('__listComponents', []),\n )\n\n registry.register(\n {\n name: 'llui_select_component',\n description:\n 'Switch the active component (the one all other tool calls target). Use a key from llui_list_components.',\n inputSchema: {\n type: 'object',\n properties: {\n key: {\n type: 'string',\n description: 'Component key as returned by llui_list_components',\n },\n },\n required: ['key'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('__selectComponent', [args.key]),\n )\n\n registry.register(\n {\n name: 'llui_replay_trace',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n importPath: {\n type: 'string',\n description:\n \"Where to import the component def from in the generated test (default: '../src/index'). Example: '../src/todo-app'.\",\n },\n exportName: {\n type: 'string',\n description:\n \"Named export that holds the component def (default: the component's name).\",\n },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const trace = (await ctx.relay!.call('exportTrace', [])) as {\n component: string\n entries: Array<{ msg: unknown; expectedState: unknown; expectedEffects: unknown[] }>\n }\n const importPath = (args.importPath as string | undefined) ?? '../src/index'\n const exportName = (args.exportName as string | undefined) ?? trace.component\n return {\n filename: `${trace.component.toLowerCase()}-replay.test.ts`,\n code: generateReplayTest(trace, importPath, exportName),\n entryCount: trace.entries.length,\n }\n },\n )\n\n registry.register(\n {\n name: 'llui_inspect_element',\n description:\n 'Get a rich report for a DOM element: tag, attributes, classes, data-*, text, bounding box, a computed-style subset (display/visibility/position/dimensions), and the bindings targeting this node. Pass a CSS selector. Returns null if no element matches.',\n inputSchema: {\n type: 'object',\n properties: { selector: { type: 'string', description: 'CSS selector' } },\n required: ['selector'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('inspectElement', [args.selector as string]),\n )\n\n registry.register(\n {\n name: 'llui_get_rendered_html',\n description:\n \"Get the outerHTML of the mounted component or a specific element. Pass 'selector' for a specific node (defaults to the mount root). Pass 'maxLength' to truncate output.\",\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string' },\n maxLength: { type: 'number' },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getRenderedHtml', [args.selector, args.maxLength]),\n )\n\n registry.register(\n {\n name: 'llui_dispatch_event',\n description:\n \"Synthesize and dispatch a browser event at a DOM element. Returns the history indices of any Msgs the handler produced plus the resulting state. 'type' is the event name (e.g. 'click', 'input', 'keydown'). 'init' is an EventInit object (e.g. { key: 'Enter' } for keydown).\",\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string' },\n type: { type: 'string' },\n init: { type: 'object' },\n },\n required: ['selector', 'type'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('dispatchDomEvent', [args.selector, args.type, args.init]),\n )\n\n registry.register(\n {\n name: 'llui_dom_diff',\n description:\n 'Compare expected HTML against the currently rendered HTML (from selector, or the mount root). Returns { match, differences }. Pass ignoreWhitespace=true to normalize whitespace.',\n inputSchema: {\n type: 'object',\n properties: {\n expected: { type: 'string' },\n selector: { type: 'string' },\n ignoreWhitespace: { type: 'boolean' },\n },\n required: ['expected'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const actual = (await ctx.relay!.call('getRenderedHtml', [args.selector])) as string\n return domDiff(String(args.expected), actual, {\n ignoreWhitespace: Boolean(args.ignoreWhitespace),\n })\n },\n )\n\n registry.register(\n {\n name: 'llui_get_focus',\n description:\n 'Return info about the currently focused element: { selector (if it has an id), tagName, selectionStart, selectionEnd }. Useful for catching \"focus lost on re-render\" bugs.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getFocus', []),\n )\n\n registry.register(\n {\n name: 'llui_lint',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {\n source: {\n type: 'string',\n description: 'TypeScript source code to lint. Mutually exclusive with `path`.',\n },\n path: {\n type: 'string',\n description:\n 'Absolute file path to read and lint (must be a .ts/.tsx file). Mutually exclusive with `source`.',\n },\n exclude: {\n type: 'array',\n items: { type: 'string' },\n description:\n \"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.\",\n },\n },\n },\n },\n 'debug-api',\n async (args, _ctx) => {\n const sourceArg = args.source as string | undefined\n const pathArg = args.path as string | undefined\n const excludeArg = args.exclude as string[] | undefined\n\n if (sourceArg !== undefined && pathArg !== undefined) {\n throw new Error(\"llui_lint: provide either 'source' or 'path', not both\")\n }\n if (sourceArg === undefined && pathArg === undefined) {\n throw new Error(\"llui_lint: must provide either 'source' or 'path'\")\n }\n\n let code: string\n let filename: string\n if (sourceArg !== undefined) {\n code = sourceArg\n filename = 'input.ts'\n } else {\n if (!pathArg!.endsWith('.ts') && !pathArg!.endsWith('.tsx')) {\n throw new Error(`llui_lint: path must end in .ts or .tsx (got ${pathArg!})`)\n }\n if (!existsSync(pathArg!)) {\n throw new Error(`llui_lint: file not found: ${pathArg!}`)\n }\n code = readFileSync(pathArg!, 'utf8')\n filename = pathArg!\n }\n\n const result = lintIdiomatic(code, filename, {\n exclude: excludeArg,\n })\n return {\n file: filename,\n score: result.score,\n violations: result.violations,\n summary: `${result.violations.length} violation(s), score ${result.score}/17`,\n }\n },\n )\n\n registry.register(\n {\n name: 'llui_force_rerender',\n description:\n \"Re-evaluate every binding's accessor against the current state, apply changed values to the DOM, and return the indices of bindings that changed. If a binding's DOM value corrects itself after this call but not after a real message, the mask for that binding is wrong.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('forceRerender', []),\n )\n\n registry.register(\n {\n name: 'llui_each_diff',\n description:\n \"Per-each-site reconciliation diffs (added/removed/moved/reused keys) from the dev-time diff log. Pass 'sinceIndex' to filter to entries after a specific message history index.\",\n inputSchema: {\n type: 'object',\n properties: { sinceIndex: { type: 'number' } },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getEachDiff', [args.sinceIndex]),\n )\n\n registry.register(\n {\n name: 'llui_scope_tree',\n description:\n \"Walk the scope tree starting at the component root (or a specific scopeId). Returns a ScopeNode tree with kind (root/show/each/branch/child/portal/foreign) and children. Pass 'depth' to limit traversal, 'scopeId' to start elsewhere.\",\n inputSchema: {\n type: 'object',\n properties: {\n depth: { type: 'number' },\n scopeId: { type: 'string' },\n },\n },\n },\n 'debug-api',\n async (args, ctx) =>\n ctx.relay!.call('getScopeTree', [{ depth: args.depth, scopeId: args.scopeId }]),\n )\n\n registry.register(\n {\n name: 'llui_disposer_log',\n description:\n \"Recent onDispose firings with scope id and cause. Pass 'limit' to cap results to the N most recent entries. Catches 'leak on branch swap' class bugs.\",\n inputSchema: {\n type: 'object',\n properties: { limit: { type: 'number' } },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getDisposerLog', [args.limit]),\n )\n\n registry.register(\n {\n name: 'llui_list_dead_bindings',\n description:\n \"Bindings that are inactive (scope disposed) OR never matched a dirty mask OR never changed value. Useful for finding wasted work and 'this never updates' bugs. Returns the subset of get_bindings with an annotation on why it's flagged.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => {\n const bindings = (await ctx.relay!.call('getBindings', [])) as Array<{\n index: number\n mask: number\n lastValue: unknown\n kind: string\n key: string | undefined\n dead: boolean\n perItem: boolean\n }>\n return bindings\n .filter((b) => b.dead || b.lastValue === undefined)\n .map((b) => ({\n ...b,\n reason: b.dead ? 'scope_disposed' : 'never_changed',\n }))\n },\n )\n\n registry.register(\n {\n name: 'llui_binding_graph',\n description:\n 'Edge list: state path → binding indices that depend on it. Inverts the compiler-emitted mask legend to show, for each top-level state field, which bindings will re-evaluate when it changes.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getBindingGraph', []),\n )\n\n registry.register(\n {\n name: 'llui_mock_effect',\n description:\n \"Register a mock for an effect matching 'match' ({ type?, payloadPath?, payloadEquals? }). The next matching effect resolves with 'response' instead of running. Mocks are one-shot; pass { persist: true } to keep across matches. Returns { mockId } for later reference.\",\n inputSchema: {\n type: 'object',\n properties: {\n match: { type: 'object' },\n response: {},\n opts: { type: 'object' },\n },\n required: ['match', 'response'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('mockEffect', [args.match, args.response, args.opts]),\n )\n\n registry.register(\n {\n name: 'llui_resolve_effect',\n description:\n \"Manually resolve a pending effect with a given response. The effect's onSuccess callback (if any) runs as if it had actually resolved. Pass effectId from llui_pending_effects.\",\n inputSchema: {\n type: 'object',\n properties: {\n effectId: { type: 'string' },\n response: {},\n },\n required: ['effectId', 'response'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('resolveEffect', [args.effectId, args.response]),\n )\n\n registry.register(\n {\n name: 'llui_pending_effects',\n description:\n \"Current queued and in-flight effects. Each entry has { id, type, dispatchedAt, status, payload }. Use 'id' with llui_resolve_effect to manually resolve one.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getPendingEffects', []),\n )\n\n registry.register(\n {\n name: 'llui_effect_timeline',\n description:\n \"Phased log of effect events: dispatched -> in-flight -> resolved/cancelled/resolved-mocked. Each entry has { effectId, type, phase, timestamp, durationMs? }. Pass 'limit' to cap the tail.\",\n inputSchema: {\n type: 'object',\n properties: { limit: { type: 'number' } },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getEffectTimeline', [args.limit]),\n )\n\n registry.register(\n {\n name: 'llui_step_back',\n description:\n \"Rewind state by replaying from init() with the last N messages excluded. 'mode' is 'pure' (default; suppresses effects) or 'live' (re-fires effects from replay). Returns the new state and rewindDepth.\",\n inputSchema: {\n type: 'object',\n properties: {\n n: { type: 'number' },\n mode: { type: 'string', enum: ['pure', 'live'] },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const n = typeof args.n === 'number' ? args.n : 1\n const mode = args.mode === 'live' ? 'live' : 'pure'\n return ctx.relay!.call('stepBack', [n, mode])\n },\n )\n\n registry.register(\n {\n name: 'llui_coverage',\n description:\n \"Per-Msg-variant coverage for the current session: { fired: { variant: { count, lastIndex } }, neverFired: [variants] }. Shows which message types have run and which haven't — useful for finding untested paths.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getCoverage', []),\n )\n\n registry.register(\n {\n name: 'llui_diff_state',\n description:\n \"Structured JSON diff between two state values. Pass 'a' and 'b' — plain objects. Returns { added, removed, changed }.\",\n inputSchema: {\n type: 'object',\n properties: {\n a: {},\n b: {},\n },\n required: ['a', 'b'],\n },\n },\n 'debug-api',\n async (args) => diffState(args.a, args.b),\n )\n\n registry.register(\n {\n name: 'llui_assert',\n description:\n \"Evaluate a predicate against current state. Pass 'path' (dot-separated), 'op' (eq/neq/exists/gt/lt/in), and 'value'. Returns { pass, actual, expected, op }.\",\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string' },\n op: { type: 'string', enum: ['eq', 'neq', 'exists', 'gt', 'lt', 'in'] },\n value: {},\n },\n required: ['path', 'op'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const actual = await ctx.relay!.call('searchState', [args.path])\n const op = args.op as string\n const expected = args.value\n let pass = false\n switch (op) {\n case 'eq':\n pass = Object.is(actual, expected)\n break\n case 'neq':\n pass = !Object.is(actual, expected)\n break\n case 'exists':\n pass = actual !== undefined\n break\n case 'gt':\n pass = typeof actual === 'number' && typeof expected === 'number' && actual > expected\n break\n case 'lt':\n pass = typeof actual === 'number' && typeof expected === 'number' && actual < expected\n break\n case 'in':\n pass = Array.isArray(expected) && expected.includes(actual)\n break\n }\n return { pass, actual, expected, op }\n },\n )\n\n registry.register(\n {\n name: 'llui_search_history',\n description:\n \"Filtered message history. Pass 'filter' with { type?, statePath?, effectType?, fromIndex?, toIndex? }. Entries match if all present fields match — type is the Msg discriminant, statePath is a dot path whose value differs pre->post, effectType is a type present in the effects array.\",\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n properties: {\n type: { type: 'string' },\n statePath: { type: 'string' },\n effectType: { type: 'string' },\n fromIndex: { type: 'number' },\n toIndex: { type: 'number' },\n },\n },\n },\n required: ['filter'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n type HRecord = {\n index: number\n timestamp: number\n msg: unknown\n stateBefore: unknown\n stateAfter: unknown\n effects: unknown[]\n dirtyMask: number\n }\n const history = (await ctx.relay!.call('getMessageHistory', [{}])) as HRecord[]\n const f = (args.filter ?? {}) as {\n type?: string\n statePath?: string\n effectType?: string\n fromIndex?: number\n toIndex?: number\n }\n function pathValue(obj: unknown, path: string): unknown {\n const parts = path.split('.')\n let v: unknown = obj\n for (const p of parts) {\n if (v == null || typeof v !== 'object') return undefined\n v = (v as Record<string, unknown>)[p]\n }\n return v\n }\n return history.filter((r) => {\n if (f.fromIndex !== undefined && r.index < f.fromIndex) return false\n if (f.toIndex !== undefined && r.index > f.toIndex) return false\n if (f.type !== undefined) {\n const t = (r.msg as { type?: string } | null)?.type\n if (t !== f.type) return false\n }\n if (f.statePath !== undefined) {\n const before = pathValue(r.stateBefore, f.statePath)\n const after = pathValue(r.stateAfter, f.statePath)\n if (Object.is(before, after)) return false\n }\n if (f.effectType !== undefined) {\n if (!r.effects.some((e) => (e as { type?: string } | null)?.type === f.effectType)) {\n return false\n }\n }\n return true\n })\n },\n )\n\n registry.register(\n {\n name: 'llui_eval',\n description:\n \"Arbitrary JavaScript in the page context via the debug relay. Returns { result, sideEffects }. 'result' is the expression's return value or { error }. 'sideEffects' makes any state changes, new history entries, new pending effects, and dirty bindings visible. Phase 1 does not support async expressions; expose async results via globalThis instead.\",\n inputSchema: {\n type: 'object',\n properties: { code: { type: 'string' } },\n required: ['code'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('evalInPage', [args.code]),\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"debug-api.js","sourceRoot":"","sources":["../../src/tools/debug-api.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from 'node:fs'\nimport { lintIdiomatic } from '@llui/lint-idiomatic'\nimport type { ToolRegistry } from '../tool-registry.js'\nimport { generateReplayTest } from './replay-test-generator.js'\nimport { domDiff, diffState } from '../util/diff.js'\n\n/**\n * Register the 23 debug-API-backed tools. Every handler here routes through\n * `ctx.relay!.call(method, args)` — which transparently dispatches to either\n * an in-process `LluiDebugAPI` or the WebSocket bridge, depending on how the\n * transport was wired.\n */\nexport function registerDebugApiTools(registry: ToolRegistry): void {\n registry.register(\n {\n name: 'llui_get_state',\n description:\n 'Get the current state of the LLui component. Returns a JSON-serializable state object.',\n inputSchema: {\n type: 'object',\n properties: {\n component: {\n type: 'string',\n description: 'Component name (defaults to root)',\n },\n },\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getState', []),\n )\n\n registry.register(\n {\n name: 'llui_send_message',\n description:\n 'Send a message to the component and return the new state and effects. Validates the message first. Calls flush() automatically.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: {\n type: 'object',\n description: 'The message to send (must be a valid Msg variant)',\n },\n },\n required: ['msg'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const errors = (await ctx.relay!.call('validateMessage', [args.msg])) as unknown[] | null\n if (errors) return { errors, sent: false }\n await ctx.relay!.call('send', [args.msg])\n await ctx.relay!.call('flush', [])\n return { state: await ctx.relay!.call('getState', []), sent: true }\n },\n )\n\n registry.register(\n {\n name: 'llui_eval_update',\n description:\n 'Dry-run: call update(state, msg) without applying. Returns what the new state and effects would be without modifying the running app.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: {\n type: 'object',\n description: 'The hypothetical message to evaluate',\n },\n },\n required: ['msg'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('evalUpdate', [args.msg]),\n )\n\n registry.register(\n {\n name: 'llui_validate_message',\n description:\n 'Validate a message against the component Msg type. Returns errors or null if valid.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: {\n type: 'object',\n description: 'The message to validate',\n },\n },\n required: ['msg'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('validateMessage', [args.msg]),\n )\n\n registry.register(\n {\n name: 'llui_get_message_history',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n since: {\n type: 'number',\n description: 'Return entries with index strictly greater than this.',\n },\n limit: {\n type: 'number',\n description: 'Max entries to return (the N most recent).',\n },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const opts: { since?: number; limit?: number } = {}\n if (typeof args.since === 'number') opts.since = args.since\n if (typeof args.limit === 'number') opts.limit = args.limit\n return ctx.relay!.call('getMessageHistory', [opts])\n },\n )\n\n registry.register(\n {\n name: 'llui_export_trace',\n description: 'Export the current session as a replayable LluiTrace JSON.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('exportTrace', []),\n )\n\n registry.register(\n {\n name: 'llui_get_bindings',\n description:\n 'Get all active reactive bindings with their masks, last values, and DOM targets.',\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'string',\n description: 'Filter by DOM selector or mask value',\n },\n },\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getBindings', []),\n )\n\n registry.register(\n {\n name: 'llui_why_did_update',\n description:\n 'Explain why a specific binding re-evaluated: which mask bits were dirty, what the accessor returned, what the previous value was.',\n inputSchema: {\n type: 'object',\n properties: {\n bindingIndex: {\n type: 'number',\n description: 'The binding index to inspect',\n },\n },\n required: ['bindingIndex'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('whyDidUpdate', [args.bindingIndex as number]),\n )\n\n registry.register(\n {\n name: 'llui_search_state',\n description:\n 'Search current state using a dot-separated path query. E.g., \"cart.items\" returns the items array.',\n inputSchema: {\n type: 'object',\n properties: {\n query: {\n type: 'string',\n description: 'Dot-separated path to search. E.g., \"user.name\", \"items\"',\n },\n },\n required: ['query'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('searchState', [args.query as string]),\n )\n\n registry.register(\n {\n name: 'llui_clear_log',\n description: 'Clear the message and effects history.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => {\n await ctx.relay!.call('clearLog', [])\n return { cleared: true }\n },\n )\n\n registry.register(\n {\n name: 'llui_list_messages',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getMessageSchema', []),\n )\n\n registry.register(\n {\n name: 'llui_decode_mask',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {\n mask: { type: 'number', description: 'The dirtyMask value to decode' },\n },\n required: ['mask'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('decodeMask', [args.mask as number]),\n )\n\n registry.register(\n {\n name: 'llui_mask_legend',\n description:\n 'Return the compiler-generated bit→field map for this component. Example: { todos: 1, filter: 2, nextId: 4 } means bit 0 represents `todos`, etc.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getMaskLegend', []),\n )\n\n registry.register(\n {\n name: 'llui_component_info',\n description:\n 'Get component name and source location (file + line) of the component() declaration. Lets you find where to read or edit the component.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getComponentInfo', []),\n )\n\n registry.register(\n {\n name: 'llui_describe_state',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getStateSchema', []),\n )\n\n registry.register(\n {\n name: 'llui_list_effects',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {},\n },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getEffectSchema', []),\n )\n\n registry.register(\n {\n name: 'llui_trace_element',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {\n selector: {\n type: 'string',\n description: 'CSS selector (e.g. `.todo.active`, `#submit`)',\n },\n },\n required: ['selector'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getBindingsFor', [args.selector as string]),\n )\n\n registry.register(\n {\n name: 'llui_snapshot_state',\n description:\n '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.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('snapshotState', []),\n )\n\n registry.register(\n {\n name: 'llui_restore_state',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n snapshot: {\n description: 'The state object returned by llui_snapshot_state.',\n },\n },\n required: ['snapshot'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n await ctx.relay!.call('restoreState', [args.snapshot])\n return { restored: true, state: await ctx.relay!.call('getState', []) }\n },\n )\n\n registry.register(\n {\n name: 'llui_list_components',\n description:\n 'List all currently-mounted LLui components + which one is active (being targeted by subsequent tool calls). Multi-mount apps show one entry per mount.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('__listComponents', []),\n )\n\n registry.register(\n {\n name: 'llui_select_component',\n description:\n 'Switch the active component (the one all other tool calls target). Use a key from llui_list_components.',\n inputSchema: {\n type: 'object',\n properties: {\n key: {\n type: 'string',\n description: 'Component key as returned by llui_list_components',\n },\n },\n required: ['key'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('__selectComponent', [args.key]),\n )\n\n registry.register(\n {\n name: 'llui_replay_trace',\n description:\n '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.',\n inputSchema: {\n type: 'object',\n properties: {\n importPath: {\n type: 'string',\n description:\n \"Where to import the component def from in the generated test (default: '../src/index'). Example: '../src/todo-app'.\",\n },\n exportName: {\n type: 'string',\n description:\n \"Named export that holds the component def (default: the component's name).\",\n },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const trace = (await ctx.relay!.call('exportTrace', [])) as {\n component: string\n entries: Array<{ msg: unknown; expectedState: unknown; expectedEffects: unknown[] }>\n }\n const importPath = (args.importPath as string | undefined) ?? '../src/index'\n const exportName = (args.exportName as string | undefined) ?? trace.component\n return {\n filename: `${trace.component.toLowerCase()}-replay.test.ts`,\n code: generateReplayTest(trace, importPath, exportName),\n entryCount: trace.entries.length,\n }\n },\n )\n\n registry.register(\n {\n name: 'llui_inspect_element',\n description:\n 'Get a rich report for a DOM element: tag, attributes, classes, data-*, text, bounding box, a computed-style subset (display/visibility/position/dimensions), and the bindings targeting this node. Pass a CSS selector. Returns null if no element matches.',\n inputSchema: {\n type: 'object',\n properties: { selector: { type: 'string', description: 'CSS selector' } },\n required: ['selector'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('inspectElement', [args.selector as string]),\n )\n\n registry.register(\n {\n name: 'llui_get_rendered_html',\n description:\n \"Get the outerHTML of the mounted component or a specific element. Pass 'selector' for a specific node (defaults to the mount root). Pass 'maxLength' to truncate output.\",\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string' },\n maxLength: { type: 'number' },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getRenderedHtml', [args.selector, args.maxLength]),\n )\n\n registry.register(\n {\n name: 'llui_dispatch_event',\n description:\n \"Synthesize and dispatch a browser event at a DOM element. Returns the history indices of any Msgs the handler produced plus the resulting state. 'type' is the event name (e.g. 'click', 'input', 'keydown'). 'init' is an EventInit object (e.g. { key: 'Enter' } for keydown).\",\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string' },\n type: { type: 'string' },\n init: { type: 'object' },\n },\n required: ['selector', 'type'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('dispatchDomEvent', [args.selector, args.type, args.init]),\n )\n\n registry.register(\n {\n name: 'llui_dom_diff',\n description:\n 'Compare expected HTML against the currently rendered HTML (from selector, or the mount root). Returns { match, differences }. Pass ignoreWhitespace=true to normalize whitespace.',\n inputSchema: {\n type: 'object',\n properties: {\n expected: { type: 'string' },\n selector: { type: 'string' },\n ignoreWhitespace: { type: 'boolean' },\n },\n required: ['expected'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const actual = (await ctx.relay!.call('getRenderedHtml', [args.selector])) as string\n return domDiff(String(args.expected), actual, {\n ignoreWhitespace: Boolean(args.ignoreWhitespace),\n })\n },\n )\n\n registry.register(\n {\n name: 'llui_get_focus',\n description:\n 'Return info about the currently focused element: { selector (if it has an id), tagName, selectionStart, selectionEnd }. Useful for catching \"focus lost on re-render\" bugs.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getFocus', []),\n )\n\n registry.register(\n {\n name: 'llui_lint',\n description:\n \"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.\",\n inputSchema: {\n type: 'object',\n properties: {\n source: {\n type: 'string',\n description: 'TypeScript source code to lint. Mutually exclusive with `path`.',\n },\n path: {\n type: 'string',\n description:\n 'Absolute file path to read and lint (must be a .ts/.tsx file). Mutually exclusive with `source`.',\n },\n exclude: {\n type: 'array',\n items: { type: 'string' },\n description:\n \"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.\",\n },\n },\n },\n },\n 'debug-api',\n async (args, _ctx) => {\n const sourceArg = args.source as string | undefined\n const pathArg = args.path as string | undefined\n const excludeArg = args.exclude as string[] | undefined\n\n if (sourceArg !== undefined && pathArg !== undefined) {\n throw new Error(\"llui_lint: provide either 'source' or 'path', not both\")\n }\n if (sourceArg === undefined && pathArg === undefined) {\n throw new Error(\"llui_lint: must provide either 'source' or 'path'\")\n }\n\n let code: string\n let filename: string\n if (sourceArg !== undefined) {\n code = sourceArg\n filename = 'input.ts'\n } else {\n if (!pathArg!.endsWith('.ts') && !pathArg!.endsWith('.tsx')) {\n throw new Error(`llui_lint: path must end in .ts or .tsx (got ${pathArg!})`)\n }\n if (!existsSync(pathArg!)) {\n throw new Error(`llui_lint: file not found: ${pathArg!}`)\n }\n code = readFileSync(pathArg!, 'utf8')\n filename = pathArg!\n }\n\n const result = lintIdiomatic(code, filename, {\n exclude: excludeArg,\n })\n return {\n file: filename,\n score: result.score,\n violations: result.violations,\n summary: `${result.violations.length} violation(s), score ${result.score}/17`,\n }\n },\n )\n\n registry.register(\n {\n name: 'llui_force_rerender',\n description:\n \"Re-evaluate every binding's accessor against the current state, apply changed values to the DOM, and return the indices of bindings that changed. If a binding's DOM value corrects itself after this call but not after a real message, the mask for that binding is wrong.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('forceRerender', []),\n )\n\n registry.register(\n {\n name: 'llui_each_diff',\n description:\n \"Per-each-site reconciliation diffs (added/removed/moved/reused keys) from the dev-time diff log. Pass 'sinceIndex' to filter to entries after a specific message history index.\",\n inputSchema: {\n type: 'object',\n properties: { sinceIndex: { type: 'number' } },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getEachDiff', [args.sinceIndex]),\n )\n\n registry.register(\n {\n name: 'llui_scope_tree',\n description:\n \"Walk the scope tree starting at the component root (or a specific scopeId). Returns a LifetimeNode tree with kind (root/show/each/branch/child/portal/foreign) and children. Pass 'depth' to limit traversal, 'scopeId' to start elsewhere.\",\n inputSchema: {\n type: 'object',\n properties: {\n depth: { type: 'number' },\n scopeId: { type: 'string' },\n },\n },\n },\n 'debug-api',\n async (args, ctx) =>\n ctx.relay!.call('getScopeTree', [{ depth: args.depth, scopeId: args.scopeId }]),\n )\n\n registry.register(\n {\n name: 'llui_disposer_log',\n description:\n \"Recent onDispose firings with scope id and cause. Pass 'limit' to cap results to the N most recent entries. Catches 'leak on branch swap' class bugs.\",\n inputSchema: {\n type: 'object',\n properties: { limit: { type: 'number' } },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getDisposerLog', [args.limit]),\n )\n\n registry.register(\n {\n name: 'llui_list_dead_bindings',\n description:\n \"Bindings that are inactive (scope disposed) OR never matched a dirty mask OR never changed value. Useful for finding wasted work and 'this never updates' bugs. Returns the subset of get_bindings with an annotation on why it's flagged.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => {\n const bindings = (await ctx.relay!.call('getBindings', [])) as Array<{\n index: number\n mask: number\n lastValue: unknown\n kind: string\n key: string | undefined\n dead: boolean\n perItem: boolean\n }>\n return bindings\n .filter((b) => b.dead || b.lastValue === undefined)\n .map((b) => ({\n ...b,\n reason: b.dead ? 'scope_disposed' : 'never_changed',\n }))\n },\n )\n\n registry.register(\n {\n name: 'llui_binding_graph',\n description:\n 'Edge list: state path → binding indices that depend on it. Inverts the compiler-emitted mask legend to show, for each top-level state field, which bindings will re-evaluate when it changes.',\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getBindingGraph', []),\n )\n\n registry.register(\n {\n name: 'llui_mock_effect',\n description:\n \"Register a mock for an effect matching 'match' ({ type?, payloadPath?, payloadEquals? }). The next matching effect resolves with 'response' instead of running. Mocks are one-shot; pass { persist: true } to keep across matches. Returns { mockId } for later reference.\",\n inputSchema: {\n type: 'object',\n properties: {\n match: { type: 'object' },\n response: {},\n opts: { type: 'object' },\n },\n required: ['match', 'response'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('mockEffect', [args.match, args.response, args.opts]),\n )\n\n registry.register(\n {\n name: 'llui_resolve_effect',\n description:\n \"Manually resolve a pending effect with a given response. The effect's onSuccess callback (if any) runs as if it had actually resolved. Pass effectId from llui_pending_effects.\",\n inputSchema: {\n type: 'object',\n properties: {\n effectId: { type: 'string' },\n response: {},\n },\n required: ['effectId', 'response'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('resolveEffect', [args.effectId, args.response]),\n )\n\n registry.register(\n {\n name: 'llui_pending_effects',\n description:\n \"Current queued and in-flight effects. Each entry has { id, type, dispatchedAt, status, payload }. Use 'id' with llui_resolve_effect to manually resolve one.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getPendingEffects', []),\n )\n\n registry.register(\n {\n name: 'llui_effect_timeline',\n description:\n \"Phased log of effect events: dispatched -> in-flight -> resolved/cancelled/resolved-mocked. Each entry has { effectId, type, phase, timestamp, durationMs? }. Pass 'limit' to cap the tail.\",\n inputSchema: {\n type: 'object',\n properties: { limit: { type: 'number' } },\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('getEffectTimeline', [args.limit]),\n )\n\n registry.register(\n {\n name: 'llui_step_back',\n description:\n \"Rewind state by replaying from init() with the last N messages excluded. 'mode' is 'pure' (default; suppresses effects) or 'live' (re-fires effects from replay). Returns the new state and rewindDepth.\",\n inputSchema: {\n type: 'object',\n properties: {\n n: { type: 'number' },\n mode: { type: 'string', enum: ['pure', 'live'] },\n },\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const n = typeof args.n === 'number' ? args.n : 1\n const mode = args.mode === 'live' ? 'live' : 'pure'\n return ctx.relay!.call('stepBack', [n, mode])\n },\n )\n\n registry.register(\n {\n name: 'llui_coverage',\n description:\n \"Per-Msg-variant coverage for the current session: { fired: { variant: { count, lastIndex } }, neverFired: [variants] }. Shows which message types have run and which haven't — useful for finding untested paths.\",\n inputSchema: { type: 'object', properties: {} },\n },\n 'debug-api',\n async (_args, ctx) => ctx.relay!.call('getCoverage', []),\n )\n\n registry.register(\n {\n name: 'llui_diff_state',\n description:\n \"Structured JSON diff between two state values. Pass 'a' and 'b' — plain objects. Returns { added, removed, changed }.\",\n inputSchema: {\n type: 'object',\n properties: {\n a: {},\n b: {},\n },\n required: ['a', 'b'],\n },\n },\n 'debug-api',\n async (args) => diffState(args.a, args.b),\n )\n\n registry.register(\n {\n name: 'llui_assert',\n description:\n \"Evaluate a predicate against current state. Pass 'path' (dot-separated), 'op' (eq/neq/exists/gt/lt/in), and 'value'. Returns { pass, actual, expected, op }.\",\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string' },\n op: { type: 'string', enum: ['eq', 'neq', 'exists', 'gt', 'lt', 'in'] },\n value: {},\n },\n required: ['path', 'op'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n const actual = await ctx.relay!.call('searchState', [args.path])\n const op = args.op as string\n const expected = args.value\n let pass = false\n switch (op) {\n case 'eq':\n pass = Object.is(actual, expected)\n break\n case 'neq':\n pass = !Object.is(actual, expected)\n break\n case 'exists':\n pass = actual !== undefined\n break\n case 'gt':\n pass = typeof actual === 'number' && typeof expected === 'number' && actual > expected\n break\n case 'lt':\n pass = typeof actual === 'number' && typeof expected === 'number' && actual < expected\n break\n case 'in':\n pass = Array.isArray(expected) && expected.includes(actual)\n break\n }\n return { pass, actual, expected, op }\n },\n )\n\n registry.register(\n {\n name: 'llui_search_history',\n description:\n \"Filtered message history. Pass 'filter' with { type?, statePath?, effectType?, fromIndex?, toIndex? }. Entries match if all present fields match — type is the Msg discriminant, statePath is a dot path whose value differs pre->post, effectType is a type present in the effects array.\",\n inputSchema: {\n type: 'object',\n properties: {\n filter: {\n type: 'object',\n properties: {\n type: { type: 'string' },\n statePath: { type: 'string' },\n effectType: { type: 'string' },\n fromIndex: { type: 'number' },\n toIndex: { type: 'number' },\n },\n },\n },\n required: ['filter'],\n },\n },\n 'debug-api',\n async (args, ctx) => {\n type HRecord = {\n index: number\n timestamp: number\n msg: unknown\n stateBefore: unknown\n stateAfter: unknown\n effects: unknown[]\n dirtyMask: number\n }\n const history = (await ctx.relay!.call('getMessageHistory', [{}])) as HRecord[]\n const f = (args.filter ?? {}) as {\n type?: string\n statePath?: string\n effectType?: string\n fromIndex?: number\n toIndex?: number\n }\n function pathValue(obj: unknown, path: string): unknown {\n const parts = path.split('.')\n let v: unknown = obj\n for (const p of parts) {\n if (v == null || typeof v !== 'object') return undefined\n v = (v as Record<string, unknown>)[p]\n }\n return v\n }\n return history.filter((r) => {\n if (f.fromIndex !== undefined && r.index < f.fromIndex) return false\n if (f.toIndex !== undefined && r.index > f.toIndex) return false\n if (f.type !== undefined) {\n const t = (r.msg as { type?: string } | null)?.type\n if (t !== f.type) return false\n }\n if (f.statePath !== undefined) {\n const before = pathValue(r.stateBefore, f.statePath)\n const after = pathValue(r.stateAfter, f.statePath)\n if (Object.is(before, after)) return false\n }\n if (f.effectType !== undefined) {\n if (!r.effects.some((e) => (e as { type?: string } | null)?.type === f.effectType)) {\n return false\n }\n }\n return true\n })\n },\n )\n\n registry.register(\n {\n name: 'llui_eval',\n description:\n \"Arbitrary JavaScript in the page context via the debug relay. Returns { result, sideEffects }. 'result' is the expression's return value or { error }. 'sideEffects' makes any state changes, new history entries, new pending effects, and dirty bindings visible. Phase 1 does not support async expressions; expose async results via globalThis instead.\",\n inputSchema: {\n type: 'object',\n properties: { code: { type: 'string' } },\n required: ['code'],\n },\n },\n 'debug-api',\n async (args, ctx) => ctx.relay!.call('evalInPage', [args.code]),\n )\n}\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { WebSocketRelayTransport } from './relay.js';
|
|
2
|
-
export type { RelayTransportOptions } from './relay.js';
|
|
1
|
+
export { WebSocketRelayTransport, RelayUnavailableError } from './relay.js';
|
|
2
|
+
export type { RelayTransportOptions, BridgeDiagnostic } from './relay.js';
|
|
3
3
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA;AAC3E,YAAY,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAA"}
|
package/dist/transports/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { WebSocketRelayTransport } from './relay.js';
|
|
1
|
+
export { WebSocketRelayTransport, RelayUnavailableError } from './relay.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export { WebSocketRelayTransport } from './relay.js'\nexport type { RelayTransportOptions } from './relay.js'\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/transports/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAA","sourcesContent":["export { WebSocketRelayTransport, RelayUnavailableError } from './relay.js'\nexport type { RelayTransportOptions, BridgeDiagnostic } from './relay.js'\n"]}
|
|
@@ -1,7 +1,54 @@
|
|
|
1
|
+
import type { Server as HttpServer } from 'node:http';
|
|
1
2
|
import type { LluiDebugAPI } from '@llui/dom';
|
|
2
3
|
import type { RelayTransport } from '../tool-registry.js';
|
|
4
|
+
/**
|
|
5
|
+
* Structured snapshot of the bridge state at a single point in time.
|
|
6
|
+
* Returned by `RelayUnavailableError.diagnostic` so tool callers can
|
|
7
|
+
* see WHY the browser isn't reachable without grepping plugin source.
|
|
8
|
+
*/
|
|
9
|
+
export interface BridgeDiagnostic {
|
|
10
|
+
connected: boolean;
|
|
11
|
+
bridge: {
|
|
12
|
+
/** Is the WS server bound and listening? */
|
|
13
|
+
running: boolean;
|
|
14
|
+
port: number | null;
|
|
15
|
+
};
|
|
16
|
+
browser: {
|
|
17
|
+
/** Currently connected browser tabs (0 or 1 — the bridge is single-client today). */
|
|
18
|
+
tabsConnected: number;
|
|
19
|
+
};
|
|
20
|
+
mcpMarker: {
|
|
21
|
+
present: boolean;
|
|
22
|
+
path: string;
|
|
23
|
+
/** Set by the Vite plugin once Vite's httpServer emits `listening`. */
|
|
24
|
+
devUrl: string | null;
|
|
25
|
+
};
|
|
26
|
+
suggestedFix: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Error thrown when a tool call needs the browser relay but no browser
|
|
30
|
+
* is attached. Carries a `diagnostic` payload the MCP handler can
|
|
31
|
+
* surface as `isError: true` tool content — no need for Claude (or any
|
|
32
|
+
* MCP client) to grep plugin source to guess what's wrong.
|
|
33
|
+
*/
|
|
34
|
+
export declare class RelayUnavailableError extends Error {
|
|
35
|
+
readonly diagnostic: BridgeDiagnostic;
|
|
36
|
+
constructor(diagnostic: BridgeDiagnostic);
|
|
37
|
+
}
|
|
3
38
|
export interface RelayTransportOptions {
|
|
4
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Either `port` (stdio mode — relay owns its own WS server) or
|
|
41
|
+
* `attachTo` (HTTP mode — relay upgrades on an existing http.Server so
|
|
42
|
+
* the MCP HTTP endpoint and the browser bridge share a single port).
|
|
43
|
+
*/
|
|
44
|
+
port?: number;
|
|
45
|
+
attachTo?: HttpServer;
|
|
46
|
+
/**
|
|
47
|
+
* Filesystem path to the MCP active-marker file. Used by `diagnose()`
|
|
48
|
+
* to check whether the Vite plugin has written the marker (indicating
|
|
49
|
+
* it's opted in) and to read back the `devUrl` it stamped.
|
|
50
|
+
*/
|
|
51
|
+
markerPath?: string;
|
|
5
52
|
onBrowserConnect?: () => void;
|
|
6
53
|
onBrowserDisconnect?: () => void;
|
|
7
54
|
}
|
|
@@ -11,6 +58,8 @@ export declare class WebSocketRelayTransport implements RelayTransport {
|
|
|
11
58
|
private pending;
|
|
12
59
|
private directApi;
|
|
13
60
|
private readonly port;
|
|
61
|
+
private readonly attachTo;
|
|
62
|
+
private readonly markerPath;
|
|
14
63
|
private readonly onConnect?;
|
|
15
64
|
private readonly onDisconnect?;
|
|
16
65
|
constructor(opts: RelayTransportOptions);
|
|
@@ -19,6 +68,13 @@ export declare class WebSocketRelayTransport implements RelayTransport {
|
|
|
19
68
|
stop(): void;
|
|
20
69
|
isAvailable(): boolean;
|
|
21
70
|
isServerRunning(): boolean;
|
|
71
|
+
/**
|
|
72
|
+
* Build a snapshot of the bridge state for diagnostics. Called when a
|
|
73
|
+
* tool call fails because the browser isn't attached — the payload
|
|
74
|
+
* goes straight into `RelayUnavailableError.diagnostic` for the
|
|
75
|
+
* client to surface.
|
|
76
|
+
*/
|
|
77
|
+
diagnose(markerPath: string): BridgeDiagnostic;
|
|
22
78
|
call(method: string, args: unknown[]): Promise<unknown>;
|
|
23
79
|
}
|
|
24
80
|
//# sourceMappingURL=relay.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/transports/relay.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"relay.d.ts","sourceRoot":"","sources":["../../src/transports/relay.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AAGrD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAEzD;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,OAAO,CAAA;IAClB,MAAM,EAAE;QACN,4CAA4C;QAC5C,OAAO,EAAE,OAAO,CAAA;QAChB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KACpB,CAAA;IACD,OAAO,EAAE;QACP,qFAAqF;QACrF,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;IACD,SAAS,EAAE;QACT,OAAO,EAAE,OAAO,CAAA;QAChB,IAAI,EAAE,MAAM,CAAA;QACZ,uEAAuE;QACvE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;KACtB,CAAA;IACD,YAAY,EAAE,MAAM,CAAA;CACrB;AAED;;;;;GAKG;AACH,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAA;gBACzB,UAAU,EAAE,gBAAgB;CAKzC;AAuCD,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,UAAU,CAAA;IACrB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC7B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAA;CACjC;AAOD,qBAAa,uBAAwB,YAAW,cAAc;IAC5D,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,SAAS,CAAyB;IAC1C,OAAO,CAAC,OAAO,CAAoC;IACnD,OAAO,CAAC,SAAS,CAA4B;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAoB;IACzC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwB;IACjD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAY;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAY;gBAE9B,IAAI,EAAE,qBAAqB;IAQvC,aAAa,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI;IAItC,KAAK,IAAI,IAAI;IA4Cb,IAAI,IAAI,IAAI;IAQZ,WAAW,IAAI,OAAO;IAItB,eAAe,IAAI,OAAO;IAI1B;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,gBAAgB;IA4BxC,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;CAqB9D"}
|
package/dist/transports/relay.js
CHANGED
|
@@ -1,15 +1,57 @@
|
|
|
1
1
|
import { WebSocketServer } from 'ws';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
/**
|
|
5
|
+
* Error thrown when a tool call needs the browser relay but no browser
|
|
6
|
+
* is attached. Carries a `diagnostic` payload the MCP handler can
|
|
7
|
+
* surface as `isError: true` tool content — no need for Claude (or any
|
|
8
|
+
* MCP client) to grep plugin source to guess what's wrong.
|
|
9
|
+
*/
|
|
10
|
+
export class RelayUnavailableError extends Error {
|
|
11
|
+
diagnostic;
|
|
12
|
+
constructor(diagnostic) {
|
|
13
|
+
super(diagnostic.suggestedFix);
|
|
14
|
+
this.name = 'RelayUnavailableError';
|
|
15
|
+
this.diagnostic = diagnostic;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function buildSuggestedFix(state) {
|
|
19
|
+
if (!state.bridgeRunning) {
|
|
20
|
+
return ('The MCP bridge server is not running. Start @llui/mcp — either via the ' +
|
|
21
|
+
'Vite plugin (install @llui/mcp as a dev dep and the plugin will auto-spawn ' +
|
|
22
|
+
'it on `pnpm dev`) or manually with `npx llui-mcp`.');
|
|
23
|
+
}
|
|
24
|
+
if (!state.markerPresent) {
|
|
25
|
+
return ('The bridge is running but no active-marker file exists — internal ' +
|
|
26
|
+
'state mismatch. Restart the MCP server and retry.');
|
|
27
|
+
}
|
|
28
|
+
if (state.devUrl === null) {
|
|
29
|
+
return ("The marker file exists but the Vite plugin hasn't stamped its dev URL " +
|
|
30
|
+
"(so the plugin probably isn't opted in). Check vite.config.ts: ensure " +
|
|
31
|
+
'`llui()` is in plugins and mcpPort is not set to false. Then restart ' +
|
|
32
|
+
'Vite and load the app in a browser.');
|
|
33
|
+
}
|
|
34
|
+
if (state.tabsConnected === 0) {
|
|
35
|
+
return (`The bridge is running and the Vite plugin is opted in, but no browser ` +
|
|
36
|
+
`tab is attached. Open ${state.devUrl} (or reload the tab if already open). ` +
|
|
37
|
+
'The browser relay connects on page load.');
|
|
38
|
+
}
|
|
39
|
+
return 'Unknown state — bridge running, browser attached, yet the call failed.';
|
|
40
|
+
}
|
|
3
41
|
export class WebSocketRelayTransport {
|
|
4
42
|
wsServer = null;
|
|
5
43
|
browserWs = null;
|
|
6
44
|
pending = new Map();
|
|
7
45
|
directApi = null;
|
|
8
46
|
port;
|
|
47
|
+
attachTo;
|
|
48
|
+
markerPath;
|
|
9
49
|
onConnect;
|
|
10
50
|
onDisconnect;
|
|
11
51
|
constructor(opts) {
|
|
12
52
|
this.port = opts.port;
|
|
53
|
+
this.attachTo = opts.attachTo;
|
|
54
|
+
this.markerPath = opts.markerPath ?? '';
|
|
13
55
|
this.onConnect = opts.onBrowserConnect;
|
|
14
56
|
this.onDisconnect = opts.onBrowserDisconnect;
|
|
15
57
|
}
|
|
@@ -19,7 +61,26 @@ export class WebSocketRelayTransport {
|
|
|
19
61
|
start() {
|
|
20
62
|
if (this.wsServer)
|
|
21
63
|
return;
|
|
22
|
-
|
|
64
|
+
// Two modes:
|
|
65
|
+
// - standalone (stdio MCP transport): own server on `port`.
|
|
66
|
+
// - attached (HTTP MCP transport): share port with MCP's http.Server
|
|
67
|
+
// via upgrade routing on `/bridge`.
|
|
68
|
+
if (this.attachTo) {
|
|
69
|
+
this.wsServer = new WebSocketServer({ noServer: true });
|
|
70
|
+
this.attachTo.on('upgrade', (req, socket, head) => {
|
|
71
|
+
if (req.url !== '/bridge')
|
|
72
|
+
return;
|
|
73
|
+
this.wsServer.handleUpgrade(req, socket, head, (ws) => {
|
|
74
|
+
this.wsServer.emit('connection', ws, req);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else if (this.port !== undefined) {
|
|
79
|
+
this.wsServer = new WebSocketServer({ port: this.port, host: '127.0.0.1' });
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
82
|
+
throw new Error('WebSocketRelayTransport: provide either `port` or `attachTo`.');
|
|
83
|
+
}
|
|
23
84
|
this.wsServer.on('connection', (ws) => {
|
|
24
85
|
this.browserWs = ws;
|
|
25
86
|
this.onConnect?.();
|
|
@@ -62,6 +123,40 @@ export class WebSocketRelayTransport {
|
|
|
62
123
|
isServerRunning() {
|
|
63
124
|
return this.wsServer !== null;
|
|
64
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Build a snapshot of the bridge state for diagnostics. Called when a
|
|
128
|
+
* tool call fails because the browser isn't attached — the payload
|
|
129
|
+
* goes straight into `RelayUnavailableError.diagnostic` for the
|
|
130
|
+
* client to surface.
|
|
131
|
+
*/
|
|
132
|
+
diagnose(markerPath) {
|
|
133
|
+
const connected = this.isAvailable();
|
|
134
|
+
const tabsConnected = this.browserWs !== null ? 1 : 0;
|
|
135
|
+
const markerPresent = existsSync(markerPath);
|
|
136
|
+
let devUrl = null;
|
|
137
|
+
if (markerPresent) {
|
|
138
|
+
try {
|
|
139
|
+
const payload = JSON.parse(readFileSync(markerPath, 'utf8'));
|
|
140
|
+
devUrl = typeof payload.devUrl === 'string' ? payload.devUrl : null;
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
// Ignore malformed markers — leaves devUrl null.
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const suggestedFix = buildSuggestedFix({
|
|
147
|
+
bridgeRunning: this.wsServer !== null,
|
|
148
|
+
tabsConnected,
|
|
149
|
+
markerPresent,
|
|
150
|
+
devUrl,
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
connected,
|
|
154
|
+
bridge: { running: this.wsServer !== null, port: this.port ?? null },
|
|
155
|
+
browser: { tabsConnected },
|
|
156
|
+
mcpMarker: { present: markerPresent, path: markerPath, devUrl },
|
|
157
|
+
suggestedFix,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
65
160
|
async call(method, args) {
|
|
66
161
|
if (this.directApi) {
|
|
67
162
|
const fn = this.directApi[method];
|
|
@@ -70,7 +165,10 @@ export class WebSocketRelayTransport {
|
|
|
70
165
|
return fn.apply(this.directApi, args);
|
|
71
166
|
}
|
|
72
167
|
if (!this.browserWs) {
|
|
73
|
-
|
|
168
|
+
// Caller will typically catch + surface the diagnostic via the
|
|
169
|
+
// MCP tool-call error path. Throwing the structured error keeps
|
|
170
|
+
// the runtime contract simple — synchronous failure with context.
|
|
171
|
+
throw new RelayUnavailableError(this.diagnose(this.markerPath || 'unknown'));
|
|
74
172
|
}
|
|
75
173
|
const id = randomUUID();
|
|
76
174
|
return new Promise((resolve, reject) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relay.js","sourceRoot":"","sources":["../../src/transports/relay.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebSocketServer, type WebSocket } from 'ws'\nimport { randomUUID } from 'node:crypto'\nimport type { LluiDebugAPI } from '@llui/dom'\nimport type { RelayTransport } from '../tool-registry.js'\n\nexport interface RelayTransportOptions {\n port: number\n onBrowserConnect?: () => void\n onBrowserDisconnect?: () => void\n}\n\ninterface PendingRequest {\n resolve: (v: unknown) => void\n reject: (e: Error) => void\n}\n\nexport class WebSocketRelayTransport implements RelayTransport {\n private wsServer: WebSocketServer | null = null\n private browserWs: WebSocket | null = null\n private pending = new Map<string, PendingRequest>()\n private directApi: LluiDebugAPI | null = null\n private readonly port: number\n private readonly onConnect?: () => void\n private readonly onDisconnect?: () => void\n\n constructor(opts: RelayTransportOptions) {\n this.port = opts.port\n this.onConnect = opts.onBrowserConnect\n this.onDisconnect = opts.onBrowserDisconnect\n }\n\n connectDirect(api: LluiDebugAPI): void {\n this.directApi = api\n }\n\n start(): void {\n if (this.wsServer) return\n this.wsServer = new WebSocketServer({ port: this.port, host: '127.0.0.1' })\n this.wsServer.on('connection', (ws) => {\n this.browserWs = ws\n this.onConnect?.()\n ws.on('message', (raw) => {\n let msg: { id: string; result?: unknown; error?: string }\n try {\n msg = JSON.parse(String(raw))\n } catch {\n return\n }\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (msg.error) p.reject(new Error(msg.error))\n else p.resolve(msg.result)\n })\n ws.on('close', () => {\n if (this.browserWs === ws) {\n this.browserWs = null\n this.onDisconnect?.()\n }\n })\n })\n }\n\n stop(): void {\n this.wsServer?.close()\n this.wsServer = null\n this.browserWs = null\n for (const p of this.pending.values()) p.reject(new Error('relay closed'))\n this.pending.clear()\n }\n\n isAvailable(): boolean {\n return this.directApi !== null || this.browserWs !== null\n }\n\n isServerRunning(): boolean {\n return this.wsServer !== null\n }\n\n async call(method: string, args: unknown[]): Promise<unknown> {\n if (this.directApi) {\n const fn = (this.directApi as unknown as Record<string, unknown>)[method]\n if (typeof fn !== 'function') throw new Error(`unknown method: ${method}`)\n return (fn as (...a: unknown[]) => unknown).apply(this.directApi, args)\n }\n if (!this.browserWs) {\n throw new Error('No browser connected to the MCP bridge. Start your dev server.')\n }\n const id = randomUUID()\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject })\n this.browserWs!.send(JSON.stringify({ id, method, args }))\n setTimeout(() => {\n if (this.pending.delete(id)) reject(new Error(`timeout: ${method}`))\n }, 5000)\n })\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"relay.js","sourceRoot":"","sources":["../../src/transports/relay.ts"],"names":[],"mappings":"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","sourcesContent":["import { WebSocketServer, type WebSocket } from 'ws'\nimport type { Server as HttpServer } from 'node:http'\nimport { existsSync, readFileSync } from 'node:fs'\nimport { randomUUID } from 'node:crypto'\nimport type { LluiDebugAPI } from '@llui/dom'\nimport type { RelayTransport } from '../tool-registry.js'\n\n/**\n * Structured snapshot of the bridge state at a single point in time.\n * Returned by `RelayUnavailableError.diagnostic` so tool callers can\n * see WHY the browser isn't reachable without grepping plugin source.\n */\nexport interface BridgeDiagnostic {\n connected: boolean\n bridge: {\n /** Is the WS server bound and listening? */\n running: boolean\n port: number | null\n }\n browser: {\n /** Currently connected browser tabs (0 or 1 — the bridge is single-client today). */\n tabsConnected: number\n }\n mcpMarker: {\n present: boolean\n path: string\n /** Set by the Vite plugin once Vite's httpServer emits `listening`. */\n devUrl: string | null\n }\n suggestedFix: string\n}\n\n/**\n * Error thrown when a tool call needs the browser relay but no browser\n * is attached. Carries a `diagnostic` payload the MCP handler can\n * surface as `isError: true` tool content — no need for Claude (or any\n * MCP client) to grep plugin source to guess what's wrong.\n */\nexport class RelayUnavailableError extends Error {\n readonly diagnostic: BridgeDiagnostic\n constructor(diagnostic: BridgeDiagnostic) {\n super(diagnostic.suggestedFix)\n this.name = 'RelayUnavailableError'\n this.diagnostic = diagnostic\n }\n}\n\nfunction buildSuggestedFix(state: {\n bridgeRunning: boolean\n tabsConnected: number\n markerPresent: boolean\n devUrl: string | null\n}): string {\n if (!state.bridgeRunning) {\n return (\n 'The MCP bridge server is not running. Start @llui/mcp — either via the ' +\n 'Vite plugin (install @llui/mcp as a dev dep and the plugin will auto-spawn ' +\n 'it on `pnpm dev`) or manually with `npx llui-mcp`.'\n )\n }\n if (!state.markerPresent) {\n return (\n 'The bridge is running but no active-marker file exists — internal ' +\n 'state mismatch. Restart the MCP server and retry.'\n )\n }\n if (state.devUrl === null) {\n return (\n \"The marker file exists but the Vite plugin hasn't stamped its dev URL \" +\n \"(so the plugin probably isn't opted in). Check vite.config.ts: ensure \" +\n '`llui()` is in plugins and mcpPort is not set to false. Then restart ' +\n 'Vite and load the app in a browser.'\n )\n }\n if (state.tabsConnected === 0) {\n return (\n `The bridge is running and the Vite plugin is opted in, but no browser ` +\n `tab is attached. Open ${state.devUrl} (or reload the tab if already open). ` +\n 'The browser relay connects on page load.'\n )\n }\n return 'Unknown state — bridge running, browser attached, yet the call failed.'\n}\n\nexport interface RelayTransportOptions {\n /**\n * Either `port` (stdio mode — relay owns its own WS server) or\n * `attachTo` (HTTP mode — relay upgrades on an existing http.Server so\n * the MCP HTTP endpoint and the browser bridge share a single port).\n */\n port?: number\n attachTo?: HttpServer\n /**\n * Filesystem path to the MCP active-marker file. Used by `diagnose()`\n * to check whether the Vite plugin has written the marker (indicating\n * it's opted in) and to read back the `devUrl` it stamped.\n */\n markerPath?: string\n onBrowserConnect?: () => void\n onBrowserDisconnect?: () => void\n}\n\ninterface PendingRequest {\n resolve: (v: unknown) => void\n reject: (e: Error) => void\n}\n\nexport class WebSocketRelayTransport implements RelayTransport {\n private wsServer: WebSocketServer | null = null\n private browserWs: WebSocket | null = null\n private pending = new Map<string, PendingRequest>()\n private directApi: LluiDebugAPI | null = null\n private readonly port: number | undefined\n private readonly attachTo: HttpServer | undefined\n private readonly markerPath: string\n private readonly onConnect?: () => void\n private readonly onDisconnect?: () => void\n\n constructor(opts: RelayTransportOptions) {\n this.port = opts.port\n this.attachTo = opts.attachTo\n this.markerPath = opts.markerPath ?? ''\n this.onConnect = opts.onBrowserConnect\n this.onDisconnect = opts.onBrowserDisconnect\n }\n\n connectDirect(api: LluiDebugAPI): void {\n this.directApi = api\n }\n\n start(): void {\n if (this.wsServer) return\n // Two modes:\n // - standalone (stdio MCP transport): own server on `port`.\n // - attached (HTTP MCP transport): share port with MCP's http.Server\n // via upgrade routing on `/bridge`.\n if (this.attachTo) {\n this.wsServer = new WebSocketServer({ noServer: true })\n this.attachTo.on('upgrade', (req, socket, head) => {\n if (req.url !== '/bridge') return\n this.wsServer!.handleUpgrade(req, socket, head, (ws) => {\n this.wsServer!.emit('connection', ws, req)\n })\n })\n } else if (this.port !== undefined) {\n this.wsServer = new WebSocketServer({ port: this.port, host: '127.0.0.1' })\n } else {\n throw new Error('WebSocketRelayTransport: provide either `port` or `attachTo`.')\n }\n this.wsServer.on('connection', (ws) => {\n this.browserWs = ws\n this.onConnect?.()\n ws.on('message', (raw) => {\n let msg: { id: string; result?: unknown; error?: string }\n try {\n msg = JSON.parse(String(raw))\n } catch {\n return\n }\n const p = this.pending.get(msg.id)\n if (!p) return\n this.pending.delete(msg.id)\n if (msg.error) p.reject(new Error(msg.error))\n else p.resolve(msg.result)\n })\n ws.on('close', () => {\n if (this.browserWs === ws) {\n this.browserWs = null\n this.onDisconnect?.()\n }\n })\n })\n }\n\n stop(): void {\n this.wsServer?.close()\n this.wsServer = null\n this.browserWs = null\n for (const p of this.pending.values()) p.reject(new Error('relay closed'))\n this.pending.clear()\n }\n\n isAvailable(): boolean {\n return this.directApi !== null || this.browserWs !== null\n }\n\n isServerRunning(): boolean {\n return this.wsServer !== null\n }\n\n /**\n * Build a snapshot of the bridge state for diagnostics. Called when a\n * tool call fails because the browser isn't attached — the payload\n * goes straight into `RelayUnavailableError.diagnostic` for the\n * client to surface.\n */\n diagnose(markerPath: string): BridgeDiagnostic {\n const connected = this.isAvailable()\n const tabsConnected = this.browserWs !== null ? 1 : 0\n const markerPresent = existsSync(markerPath)\n let devUrl: string | null = null\n if (markerPresent) {\n try {\n const payload = JSON.parse(readFileSync(markerPath, 'utf8')) as { devUrl?: string }\n devUrl = typeof payload.devUrl === 'string' ? payload.devUrl : null\n } catch {\n // Ignore malformed markers — leaves devUrl null.\n }\n }\n const suggestedFix = buildSuggestedFix({\n bridgeRunning: this.wsServer !== null,\n tabsConnected,\n markerPresent,\n devUrl,\n })\n return {\n connected,\n bridge: { running: this.wsServer !== null, port: this.port ?? null },\n browser: { tabsConnected },\n mcpMarker: { present: markerPresent, path: markerPath, devUrl },\n suggestedFix,\n }\n }\n\n async call(method: string, args: unknown[]): Promise<unknown> {\n if (this.directApi) {\n const fn = (this.directApi as unknown as Record<string, unknown>)[method]\n if (typeof fn !== 'function') throw new Error(`unknown method: ${method}`)\n return (fn as (...a: unknown[]) => unknown).apply(this.directApi, args)\n }\n if (!this.browserWs) {\n // Caller will typically catch + surface the diagnostic via the\n // MCP tool-call error path. Throwing the structured error keeps\n // the runtime contract simple — synchronous failure with context.\n throw new RelayUnavailableError(this.diagnose(this.markerPath || 'unknown'))\n }\n const id = randomUUID()\n return new Promise((resolve, reject) => {\n this.pending.set(id, { resolve, reject })\n this.browserWs!.send(JSON.stringify({ id, method, args }))\n setTimeout(() => {\n if (this.pending.delete(id)) reject(new Error(`timeout: ${method}`))\n }, 5000)\n })\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llui/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"bin": {
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
"dist"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
19
20
|
"ws": "^8.18.0",
|
|
20
|
-
"@llui/dom": "0.0.
|
|
21
|
+
"@llui/dom": "0.0.22",
|
|
21
22
|
"@llui/lint-idiomatic": "0.0.12"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|