@llui/mcp 0.0.22 → 0.0.24

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.
Files changed (41) hide show
  1. package/dist/cli.js +27 -2
  2. package/dist/cli.js.map +1 -1
  3. package/dist/index.d.ts +12 -0
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +17 -3
  6. package/dist/index.js.map +1 -1
  7. package/dist/tool-registry.d.ts +48 -0
  8. package/dist/tool-registry.d.ts.map +1 -1
  9. package/dist/tool-registry.js.map +1 -1
  10. package/dist/tools/cdp.d.ts +3 -0
  11. package/dist/tools/cdp.d.ts.map +1 -0
  12. package/dist/tools/cdp.js +130 -0
  13. package/dist/tools/cdp.js.map +1 -0
  14. package/dist/tools/compiler.d.ts +3 -0
  15. package/dist/tools/compiler.d.ts.map +1 -0
  16. package/dist/tools/compiler.js +60 -0
  17. package/dist/tools/compiler.js.map +1 -0
  18. package/dist/tools/debug-api.d.ts.map +1 -1
  19. package/dist/tools/debug-api.js +36 -41
  20. package/dist/tools/debug-api.js.map +1 -1
  21. package/dist/tools/index.d.ts +4 -0
  22. package/dist/tools/index.d.ts.map +1 -1
  23. package/dist/tools/index.js +4 -0
  24. package/dist/tools/index.js.map +1 -1
  25. package/dist/tools/source.d.ts +3 -0
  26. package/dist/tools/source.d.ts.map +1 -0
  27. package/dist/tools/source.js +152 -0
  28. package/dist/tools/source.js.map +1 -0
  29. package/dist/tools/ssr.d.ts +3 -0
  30. package/dist/tools/ssr.d.ts.map +1 -0
  31. package/dist/tools/ssr.js +58 -0
  32. package/dist/tools/ssr.js.map +1 -0
  33. package/dist/transports/cdp.d.ts +51 -0
  34. package/dist/transports/cdp.d.ts.map +1 -0
  35. package/dist/transports/cdp.js +237 -0
  36. package/dist/transports/cdp.js.map +1 -0
  37. package/dist/transports/index.d.ts +1 -0
  38. package/dist/transports/index.d.ts.map +1 -1
  39. package/dist/transports/index.js +1 -0
  40. package/dist/transports/index.js.map +1 -1
  41. package/package.json +13 -4
package/dist/cli.js CHANGED
@@ -20,6 +20,22 @@ function parseHttpFlag(argv) {
20
20
  }
21
21
  return Number(process.env.LLUI_MCP_PORT ?? 5200);
22
22
  }
23
+ /**
24
+ * Parse `--url <url>` from argv. Returns the URL if present, null otherwise.
25
+ */
26
+ function parseUrlFlag(argv) {
27
+ const idx = argv.indexOf('--url');
28
+ if (idx < 0)
29
+ return null;
30
+ const next = argv[idx + 1];
31
+ return next && !next.startsWith('-') ? next : null;
32
+ }
33
+ /**
34
+ * Parse `--headed` from argv. Returns true if the flag is present.
35
+ */
36
+ function parseHeadedFlag(argv) {
37
+ return argv.includes('--headed');
38
+ }
23
39
  const bridgePort = Number(process.env.LLUI_MCP_PORT ?? 5200);
24
40
  const args = process.argv.slice(2);
25
41
  const httpPort = parseHttpFlag(args);
@@ -40,7 +56,11 @@ async function main() {
40
56
  // Stdio mode — Claude's `.mcp.json` spawns llui-mcp and talks over
41
57
  // stdin/stdout. The bridge runs on its own WebSocket server on
42
58
  // `bridgePort`.
43
- const server = new LluiMcpServer(bridgePort);
59
+ const server = new LluiMcpServer({
60
+ bridgePort,
61
+ devUrl: parseUrlFlag(args) ?? undefined,
62
+ headed: parseHeadedFlag(args),
63
+ });
44
64
  server.startBridge();
45
65
  const transport = new StdioServerTransport();
46
66
  await server.connect(transport);
@@ -69,7 +89,12 @@ async function main() {
69
89
  // file. All MCP sessions route tool calls through its relay via
70
90
  // `createSessionMcp()` — ensures the browser-connected state is
71
91
  // shared instead of each session creating its own dead relay.
72
- const bridgeHost = new LluiMcpServer({ bridgePort: httpPort, attachTo: httpServer });
92
+ const bridgeHost = new LluiMcpServer({
93
+ bridgePort: httpPort,
94
+ attachTo: httpServer,
95
+ devUrl: parseUrlFlag(args) ?? undefined,
96
+ headed: parseHeadedFlag(args),
97
+ });
73
98
  bridgeHost.startBridge();
74
99
  httpServer.listen(httpPort, '127.0.0.1', () => {
75
100
  process.stderr.write(`[llui-mcp] HTTP transport on http://127.0.0.1:${httpPort}/mcp; bridge ws://127.0.0.1:${httpPort}/bridge\n`);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
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 host: owns the WS relay, tool registry, and marker\n // file. All MCP sessions route tool calls through its relay via\n // `createSessionMcp()` — ensures the browser-connected state is\n // shared instead of each session creating its own dead relay.\n const bridgeHost = new LluiMcpServer({ bridgePort: httpPort, attachTo: httpServer })\n bridgeHost.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 bridgeHost.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. SDK requires one `McpServer` per transport, but\n // all sessions must share the single browser bridge — route\n // through `createSessionMcp()` so the session's tool dispatch\n // lands on bridgeHost's registry + relay.\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 sessionMcp = bridgeHost.createSessionMcp()\n await sessionMcp.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 //\n // Glyphs: emoji ✓/✗ by default, fall back to `OK`/`FAIL` when the\n // environment requests plain output. Honors `--plain` and the\n // standard `NO_COLOR` env var (https://no-color.org).\n const plain = args.includes('--plain') || process.env.NO_COLOR !== undefined\n const ok = plain ? 'OK ' : '✓'\n const fail = plain ? 'FAIL' : '✗'\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 ? ok : fail} ${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"]}
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\n/**\n * Parse `--url <url>` from argv. Returns the URL if present, null otherwise.\n */\nfunction parseUrlFlag(argv: string[]): string | null {\n const idx = argv.indexOf('--url')\n if (idx < 0) return null\n const next = argv[idx + 1]\n return next && !next.startsWith('-') ? next : null\n}\n\n/**\n * Parse `--headed` from argv. Returns true if the flag is present.\n */\nfunction parseHeadedFlag(argv: string[]): boolean {\n return argv.includes('--headed')\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({\n bridgePort,\n devUrl: parseUrlFlag(args) ?? undefined,\n headed: parseHeadedFlag(args),\n })\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 host: owns the WS relay, tool registry, and marker\n // file. All MCP sessions route tool calls through its relay via\n // `createSessionMcp()` — ensures the browser-connected state is\n // shared instead of each session creating its own dead relay.\n const bridgeHost = new LluiMcpServer({\n bridgePort: httpPort,\n attachTo: httpServer,\n devUrl: parseUrlFlag(args) ?? undefined,\n headed: parseHeadedFlag(args),\n })\n bridgeHost.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 bridgeHost.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. SDK requires one `McpServer` per transport, but\n // all sessions must share the single browser bridge — route\n // through `createSessionMcp()` so the session's tool dispatch\n // lands on bridgeHost's registry + relay.\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 sessionMcp = bridgeHost.createSessionMcp()\n await sessionMcp.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 //\n // Glyphs: emoji ✓/✗ by default, fall back to `OK`/`FAIL` when the\n // environment requests plain output. Honors `--plain` and the\n // standard `NO_COLOR` env var (https://no-color.org).\n const plain = args.includes('--plain') || process.env.NO_COLOR !== undefined\n const ok = plain ? 'OK ' : '✓'\n const fail = plain ? 'FAIL' : '✗'\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 ? ok : fail} ${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
@@ -42,12 +42,24 @@ export interface LluiMcpServerOptions {
42
42
  * connect).
43
43
  */
44
44
  attachTo?: HttpServer;
45
+ /**
46
+ * Optional dev-server URL for CDP fallback navigation. When provided,
47
+ * the CDP session manager will use this URL as the target for Playwright
48
+ * browser instances.
49
+ */
50
+ devUrl?: string;
51
+ /**
52
+ * Whether to run the Playwright browser in headed mode (visible window).
53
+ * Defaults to false (headless).
54
+ */
55
+ headed?: boolean;
45
56
  }
46
57
  export declare class LluiMcpServer {
47
58
  private readonly registry;
48
59
  private readonly relay;
49
60
  private readonly bridgePort;
50
61
  private readonly mcp;
62
+ private readonly cdp;
51
63
  private devUrl;
52
64
  /**
53
65
  * @param optsOrPort options object (preferred) or bridge port (legacy).
@@ -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;AAI7C,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAA;AAE9E,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AA0BxF;;;;;;;;;;;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;IAEpC;;;;;;;;;OASG;gBACS,UAAU,GAAE,oBAAoB,GAAG,MAAa;IAqB5D;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAwCtB;;;;;;;;;;OAUG;IACH,gBAAgB,IAAI,SAAS;IAI7B;;;;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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAI7C,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,WAAW,CAAA;AACrD,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAC/E,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,+CAA+C,CAAA;AAE9E,OAAO,EAAkC,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAA;AAoCxF;;;;;;;;;;;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;IACrB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;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,QAAQ,CAAC,GAAG,CAAmB;IACvC,OAAO,CAAC,MAAM,CAAsB;IAEpC;;;;;;;;;OASG;gBACS,UAAU,GAAE,oBAAoB,GAAG,MAAa;IA6B5D;;;;;;;OAOG;IACH,OAAO,CAAC,cAAc;IAwCtB;;;;;;;;;;OAUG;IACH,gBAAgB,IAAI,SAAS;IAI7B;;;;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;IAM5B;;;;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,EAQ3C,CAAA"}
package/dist/index.js CHANGED
@@ -4,8 +4,8 @@ import { fileURLToPath } from 'node:url';
4
4
  import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
5
5
  import { ListToolsRequestSchema, CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
6
  import { ToolRegistry } from './tool-registry.js';
7
- import { registerDebugApiTools } from './tools/index.js';
8
- import { WebSocketRelayTransport, RelayUnavailableError } from './transports/index.js';
7
+ import { registerDebugApiTools, registerCdpTools, registerCompilerTools, registerSourceTools, registerSsrTools, } from './tools/index.js';
8
+ import { WebSocketRelayTransport, RelayUnavailableError, CdpSessionManager, } from './transports/index.js';
9
9
  /**
10
10
  * Version advertised in the MCP `initialize` handshake. Read once from
11
11
  * our own `package.json` so it stays in sync with the publish bump,
@@ -75,6 +75,7 @@ export class LluiMcpServer {
75
75
  relay;
76
76
  bridgePort;
77
77
  mcp;
78
+ cdp;
78
79
  devUrl = null;
79
80
  /**
80
81
  * @param optsOrPort options object (preferred) or bridge port (legacy).
@@ -99,7 +100,15 @@ export class LluiMcpServer {
99
100
  attachTo: opts.attachTo,
100
101
  markerPath: mcpActiveFilePath(),
101
102
  });
103
+ this.cdp = new CdpSessionManager({
104
+ devUrl: opts.devUrl ?? null,
105
+ headed: opts.headed ?? false,
106
+ });
102
107
  registerDebugApiTools(this.registry);
108
+ registerCdpTools(this.registry);
109
+ registerCompilerTools(this.registry);
110
+ registerSourceTools(this.registry);
111
+ registerSsrTools(this.registry);
103
112
  // SDK-managed MCP server — owns the JSON-RPC protocol, handshake,
104
113
  // session lifecycle. Transport is plugged in later via `connect()`.
105
114
  this.mcp = this.buildMcpServer();
@@ -183,6 +192,7 @@ export class LluiMcpServer {
183
192
  */
184
193
  setDevUrl(url) {
185
194
  this.devUrl = url;
195
+ this.cdp.setDevUrl(url);
186
196
  if (this.relay.isServerRunning())
187
197
  this.writeActiveFile();
188
198
  }
@@ -233,7 +243,7 @@ export class LluiMcpServer {
233
243
  }
234
244
  /** Handle an MCP tool call */
235
245
  async handleToolCall(name, args) {
236
- const ctx = { relay: this.relay, cdp: null };
246
+ const ctx = { relay: this.relay, cdp: this.cdp };
237
247
  return this.registry.dispatch(name, args, ctx);
238
248
  }
239
249
  }
@@ -245,6 +255,10 @@ export class LluiMcpServer {
245
255
  export const mcpToolDefinitions = (() => {
246
256
  const registry = new ToolRegistry();
247
257
  registerDebugApiTools(registry);
258
+ registerCdpTools(registry);
259
+ registerCompilerTools(registry);
260
+ registerSourceTools(registry);
261
+ registerSsrTools(registry);
248
262
  return registry.listDefinitions();
249
263
  })();
250
264
  //# sourceMappingURL=index.js.map
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, readFileSync, writeFileSync, unlinkSync, existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\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 * Version advertised in the MCP `initialize` handshake. Read once from\n * our own `package.json` so it stays in sync with the publish bump,\n * instead of a hardcoded literal that silently drifts each release.\n *\n * Falls back to `'unknown'` on read failure — SDK initialization still\n * succeeds; only the cosmetic serverInfo.version is affected.\n */\nconst PACKAGE_VERSION: string = (() => {\n try {\n // dist layout: `dist/index.js` → `package.json` is two levels up\n // from the module file at runtime.\n const here = dirname(fileURLToPath(import.meta.url))\n const pkg = JSON.parse(readFileSync(resolve(here, '../package.json'), 'utf8')) as {\n version?: string\n }\n return typeof pkg.version === 'string' ? pkg.version : 'unknown'\n } catch {\n return 'unknown'\n }\n})()\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 /**\n * @param optsOrPort options object (preferred) or bridge port (legacy).\n * The numeric-port form is kept for one release cycle of back-compat;\n * new code should always pass an options object. The options form\n * supports `attachTo` for HTTP-transport deployments that share a\n * single port between MCP and the browser bridge — the numeric form\n * can't express that.\n * @deprecated numeric `optsOrPort` — pass `{ bridgePort }` instead.\n * This overload will be removed in a future breaking release.\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 // Pass bridgePort even in attachTo mode — the relay's diagnose()\n // needs it for the port field of BridgeDiagnostic. The `start()`\n // path is gated on `attachTo` first so a standalone listener\n // never gets created twice.\n this.relay = new WebSocketRelayTransport({\n port: 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 = this.buildMcpServer()\n }\n\n /**\n * Build a fresh SDK `McpServer` wired to THIS instance's tool\n * registry and browser relay. The primary `this.mcp` uses one.\n * `createSessionMcp()` returns additional ones for HTTP-transport\n * deployments where every session needs its own SDK Server — each\n * routes tool calls through the shared relay, so the single\n * bridgeHost owns all the browser-facing state.\n */\n private buildMcpServer(): McpServer {\n const mcp = new McpServer(\n { name: '@llui/mcp', version: PACKAGE_VERSION },\n { capabilities: { tools: {} } },\n )\n 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 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 return mcp\n }\n\n /**\n * Build a new SDK MCP server sharing this instance's registry + relay,\n * for HTTP-transport deployments where each session needs its own\n * `Server` (SDK requirement). Call-site pattern:\n *\n * const bridgeHost = new LluiMcpServer({ bridgePort, attachTo: httpServer })\n * bridgeHost.startBridge()\n * // Per session:\n * const sessionMcp = bridgeHost.createSessionMcp()\n * await sessionMcp.connect(transport)\n */\n createSessionMcp(): McpServer {\n return this.buildMcpServer()\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"]}
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, readFileSync, writeFileSync, unlinkSync, existsSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\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 {\n registerDebugApiTools,\n registerCdpTools,\n registerCompilerTools,\n registerSourceTools,\n registerSsrTools,\n} from './tools/index.js'\nimport {\n WebSocketRelayTransport,\n RelayUnavailableError,\n CdpSessionManager,\n} from './transports/index.js'\n\n/**\n * Version advertised in the MCP `initialize` handshake. Read once from\n * our own `package.json` so it stays in sync with the publish bump,\n * instead of a hardcoded literal that silently drifts each release.\n *\n * Falls back to `'unknown'` on read failure — SDK initialization still\n * succeeds; only the cosmetic serverInfo.version is affected.\n */\nconst PACKAGE_VERSION: string = (() => {\n try {\n // dist layout: `dist/index.js` → `package.json` is two levels up\n // from the module file at runtime.\n const here = dirname(fileURLToPath(import.meta.url))\n const pkg = JSON.parse(readFileSync(resolve(here, '../package.json'), 'utf8')) as {\n version?: string\n }\n return typeof pkg.version === 'string' ? pkg.version : 'unknown'\n } catch {\n return 'unknown'\n }\n})()\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 * Optional dev-server URL for CDP fallback navigation. When provided,\n * the CDP session manager will use this URL as the target for Playwright\n * browser instances.\n */\n devUrl?: string\n /**\n * Whether to run the Playwright browser in headed mode (visible window).\n * Defaults to false (headless).\n */\n headed?: boolean\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 readonly cdp: CdpSessionManager\n private devUrl: string | null = null\n\n /**\n * @param optsOrPort options object (preferred) or bridge port (legacy).\n * The numeric-port form is kept for one release cycle of back-compat;\n * new code should always pass an options object. The options form\n * supports `attachTo` for HTTP-transport deployments that share a\n * single port between MCP and the browser bridge — the numeric form\n * can't express that.\n * @deprecated numeric `optsOrPort` — pass `{ bridgePort }` instead.\n * This overload will be removed in a future breaking release.\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 // Pass bridgePort even in attachTo mode — the relay's diagnose()\n // needs it for the port field of BridgeDiagnostic. The `start()`\n // path is gated on `attachTo` first so a standalone listener\n // never gets created twice.\n this.relay = new WebSocketRelayTransport({\n port: this.bridgePort,\n attachTo: opts.attachTo,\n markerPath: mcpActiveFilePath(),\n })\n this.cdp = new CdpSessionManager({\n devUrl: opts.devUrl ?? null,\n headed: opts.headed ?? false,\n })\n registerDebugApiTools(this.registry)\n registerCdpTools(this.registry)\n registerCompilerTools(this.registry)\n registerSourceTools(this.registry)\n registerSsrTools(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 = this.buildMcpServer()\n }\n\n /**\n * Build a fresh SDK `McpServer` wired to THIS instance's tool\n * registry and browser relay. The primary `this.mcp` uses one.\n * `createSessionMcp()` returns additional ones for HTTP-transport\n * deployments where every session needs its own SDK Server — each\n * routes tool calls through the shared relay, so the single\n * bridgeHost owns all the browser-facing state.\n */\n private buildMcpServer(): McpServer {\n const mcp = new McpServer(\n { name: '@llui/mcp', version: PACKAGE_VERSION },\n { capabilities: { tools: {} } },\n )\n 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 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 return mcp\n }\n\n /**\n * Build a new SDK MCP server sharing this instance's registry + relay,\n * for HTTP-transport deployments where each session needs its own\n * `Server` (SDK requirement). Call-site pattern:\n *\n * const bridgeHost = new LluiMcpServer({ bridgePort, attachTo: httpServer })\n * bridgeHost.startBridge()\n * // Per session:\n * const sessionMcp = bridgeHost.createSessionMcp()\n * await sessionMcp.connect(transport)\n */\n createSessionMcp(): McpServer {\n return this.buildMcpServer()\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 this.cdp.setDevUrl(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: this.cdp }\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 registerCdpTools(registry)\n registerCompilerTools(registry)\n registerSourceTools(registry)\n registerSsrTools(registry)\n return registry.listDefinitions()\n})()\n"]}
@@ -16,9 +16,57 @@ export interface RelayTransport {
16
16
  call(method: string, args: unknown[]): Promise<unknown>;
17
17
  isAvailable(): boolean;
18
18
  }
19
+ export interface ConsoleEntry {
20
+ level: 'log' | 'info' | 'warn' | 'error' | 'debug';
21
+ text: string;
22
+ timestamp: number;
23
+ stackTrace?: string;
24
+ }
25
+ export interface NetworkEntry {
26
+ requestId: string;
27
+ url: string;
28
+ method: string;
29
+ status: number | null;
30
+ startTime: number;
31
+ endTime: number | null;
32
+ durationMs: number | null;
33
+ failed: boolean;
34
+ failureReason?: string;
35
+ }
36
+ export interface ErrorEntry {
37
+ text: string;
38
+ stack: string;
39
+ timestamp: number;
40
+ url?: string;
41
+ line?: number;
42
+ column?: number;
43
+ }
19
44
  export interface CdpTransport {
20
45
  call(domain: string, method: string, params?: Record<string, unknown>): Promise<unknown>;
21
46
  isAvailable(): boolean;
47
+ screenshot(opts: {
48
+ selector?: string;
49
+ fullPage?: boolean;
50
+ format?: 'png' | 'jpeg';
51
+ }): Promise<{
52
+ data: string;
53
+ format: string;
54
+ mimeType: string;
55
+ }>;
56
+ accessibilitySnapshot(opts: {
57
+ selector?: string;
58
+ interestingOnly?: boolean;
59
+ }): Promise<unknown>;
60
+ getConsoleBuffer(limit?: number, level?: string): ConsoleEntry[];
61
+ getNetworkBuffer(limit?: number, filter?: {
62
+ urlPattern?: string;
63
+ status?: number;
64
+ }): NetworkEntry[];
65
+ getErrorBuffer(limit?: number): ErrorEntry[];
66
+ closeBrowser(): Promise<{
67
+ closed: boolean;
68
+ reason?: string;
69
+ }>;
22
70
  }
23
71
  export type ToolHandler = (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>;
24
72
  export declare class ToolRegistry {
@@ -1 +1 @@
1
- {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ,CAAA;QACd,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACnC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;KACpB,CAAA;CACF;AAED,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,KAAK,GAAG,QAAQ,GAAG,UAAU,CAAA;AAEnE,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,cAAc,GAAG,IAAI,CAAA;IAC5B,GAAG,EAAE,YAAY,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACvD,WAAW,IAAI,OAAO,CAAA;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;IACxF,WAAW,IAAI,OAAO,CAAA;CACvB;AAED,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,OAAO,CAAC,CAAA;AAQ/F,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAA2B;IAE1C,QAAQ,CAAC,UAAU,EAAE,cAAc,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,WAAW,GAAG,IAAI;IAO5E,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;IAM/F,eAAe,IAAI,cAAc,EAAE;IAInC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;CAGzC"}
1
+ {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AAmCA,MAAM,OAAO,YAAY;IACf,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAA;IAE1C,QAAQ,CAAC,UAA0B,EAAE,KAAgB,EAAE,OAAoB;QACzE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAA;QACpE,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,IAA6B,EAAE,GAAgB;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACpC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAA;QACpD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACjC,CAAC;IAED,eAAe;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IACnE,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,CAAA;IAC9C,CAAC;CACF","sourcesContent":["export interface ToolDefinition {\n name: string\n description: string\n inputSchema: {\n type: 'object'\n properties: Record<string, unknown>\n required?: string[]\n }\n}\n\nexport type ToolLayer = 'debug-api' | 'cdp' | 'source' | 'compiler'\n\nexport interface ToolContext {\n relay: RelayTransport | null\n cdp: CdpTransport | null\n}\n\nexport interface RelayTransport {\n call(method: string, args: unknown[]): Promise<unknown>\n isAvailable(): boolean\n}\n\nexport interface CdpTransport {\n call(domain: string, method: string, params?: Record<string, unknown>): Promise<unknown>\n isAvailable(): boolean\n}\n\nexport type ToolHandler = (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>\n\ninterface Entry {\n definition: ToolDefinition\n layer: ToolLayer\n handler: ToolHandler\n}\n\nexport class ToolRegistry {\n private entries = new Map<string, Entry>()\n\n register(definition: ToolDefinition, layer: ToolLayer, handler: ToolHandler): void {\n if (this.entries.has(definition.name)) {\n throw new Error(`Duplicate tool registration: ${definition.name}`)\n }\n this.entries.set(definition.name, { definition, layer, handler })\n }\n\n async dispatch(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<unknown> {\n const entry = this.entries.get(name)\n if (!entry) throw new Error(`Unknown tool: ${name}`)\n return entry.handler(args, ctx)\n }\n\n listDefinitions(): ToolDefinition[] {\n return Array.from(this.entries.values()).map((e) => e.definition)\n }\n\n getLayer(name: string): ToolLayer | null {\n return this.entries.get(name)?.layer ?? null\n }\n}\n"]}
1
+ {"version":3,"file":"tool-registry.js","sourceRoot":"","sources":["../src/tool-registry.ts"],"names":[],"mappings":"AA4EA,MAAM,OAAO,YAAY;IACf,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAA;IAE1C,QAAQ,CAAC,UAA0B,EAAE,KAAgB,EAAE,OAAoB;QACzE,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,KAAK,CAAC,gCAAgC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAA;QACpE,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAA;IACnE,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,IAAY,EAAE,IAA6B,EAAE,GAAgB;QAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QACpC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAA;QACpD,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACjC,CAAC;IAED,eAAe;QACb,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAA;IACnE,CAAC;IAED,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,KAAK,IAAI,IAAI,CAAA;IAC9C,CAAC;CACF","sourcesContent":["export interface ToolDefinition {\n name: string\n description: string\n inputSchema: {\n type: 'object'\n properties: Record<string, unknown>\n required?: string[]\n }\n}\n\nexport type ToolLayer = 'debug-api' | 'cdp' | 'source' | 'compiler'\n\nexport interface ToolContext {\n relay: RelayTransport | null\n cdp: CdpTransport | null\n}\n\nexport interface RelayTransport {\n call(method: string, args: unknown[]): Promise<unknown>\n isAvailable(): boolean\n}\n\nexport interface ConsoleEntry {\n level: 'log' | 'info' | 'warn' | 'error' | 'debug'\n text: string\n timestamp: number\n stackTrace?: string\n}\n\nexport interface NetworkEntry {\n requestId: string\n url: string\n method: string\n status: number | null\n startTime: number\n endTime: number | null\n durationMs: number | null\n failed: boolean\n failureReason?: string\n}\n\nexport interface ErrorEntry {\n text: string\n stack: string\n timestamp: number\n url?: string\n line?: number\n column?: number\n}\n\nexport interface CdpTransport {\n call(domain: string, method: string, params?: Record<string, unknown>): Promise<unknown>\n isAvailable(): boolean\n screenshot(opts: {\n selector?: string\n fullPage?: boolean\n format?: 'png' | 'jpeg'\n }): Promise<{ data: string; format: string; mimeType: string }>\n accessibilitySnapshot(opts: { selector?: string; interestingOnly?: boolean }): Promise<unknown>\n getConsoleBuffer(limit?: number, level?: string): ConsoleEntry[]\n getNetworkBuffer(\n limit?: number,\n filter?: { urlPattern?: string; status?: number },\n ): NetworkEntry[]\n getErrorBuffer(limit?: number): ErrorEntry[]\n closeBrowser(): Promise<{ closed: boolean; reason?: string }>\n}\n\nexport type ToolHandler = (args: Record<string, unknown>, ctx: ToolContext) => Promise<unknown>\n\ninterface Entry {\n definition: ToolDefinition\n layer: ToolLayer\n handler: ToolHandler\n}\n\nexport class ToolRegistry {\n private entries = new Map<string, Entry>()\n\n register(definition: ToolDefinition, layer: ToolLayer, handler: ToolHandler): void {\n if (this.entries.has(definition.name)) {\n throw new Error(`Duplicate tool registration: ${definition.name}`)\n }\n this.entries.set(definition.name, { definition, layer, handler })\n }\n\n async dispatch(name: string, args: Record<string, unknown>, ctx: ToolContext): Promise<unknown> {\n const entry = this.entries.get(name)\n if (!entry) throw new Error(`Unknown tool: ${name}`)\n return entry.handler(args, ctx)\n }\n\n listDefinitions(): ToolDefinition[] {\n return Array.from(this.entries.values()).map((e) => e.definition)\n }\n\n getLayer(name: string): ToolLayer | null {\n return this.entries.get(name)?.layer ?? null\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ToolRegistry } from '../tool-registry.js';
2
+ export declare function registerCdpTools(registry: ToolRegistry): void;
3
+ //# sourceMappingURL=cdp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp.d.ts","sourceRoot":"","sources":["../../src/tools/cdp.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAyJ7D"}
@@ -0,0 +1,130 @@
1
+ export function registerCdpTools(registry) {
2
+ registry.register({
3
+ name: 'llui_screenshot',
4
+ description: 'Capture a screenshot of the browser page or a specific element. Returns base64-encoded PNG or JPEG. Requires CDP transport (browser attached via :9222 or Playwright fallback).',
5
+ inputSchema: {
6
+ type: 'object',
7
+ properties: {
8
+ selector: { type: 'string', description: 'CSS selector — screenshot only this element' },
9
+ fullPage: {
10
+ type: 'boolean',
11
+ description: 'Capture full scrollable page (default false)',
12
+ },
13
+ format: {
14
+ type: 'string',
15
+ enum: ['png', 'jpeg'],
16
+ description: 'Image format (default png)',
17
+ },
18
+ },
19
+ },
20
+ }, 'cdp', async (args, ctx) => {
21
+ if (!ctx.cdp)
22
+ return cdpUnavailable();
23
+ return ctx.cdp.screenshot({
24
+ selector: args.selector,
25
+ fullPage: args.fullPage,
26
+ format: args.format,
27
+ });
28
+ });
29
+ registry.register({
30
+ name: 'llui_a11y_tree',
31
+ description: 'Return the accessibility tree for the page or a specific element. Useful for verifying ARIA roles, labels, and keyboard navigation structure.',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ selector: {
36
+ type: 'string',
37
+ description: 'Root element CSS selector (default: full page)',
38
+ },
39
+ interestingOnly: {
40
+ type: 'boolean',
41
+ description: 'Omit nodes with no accessibility-relevant attributes (default true)',
42
+ },
43
+ },
44
+ },
45
+ }, 'cdp', async (args, ctx) => {
46
+ if (!ctx.cdp)
47
+ return cdpUnavailable();
48
+ return ctx.cdp.accessibilitySnapshot({
49
+ selector: args.selector,
50
+ interestingOnly: args.interestingOnly ?? true,
51
+ });
52
+ });
53
+ registry.register({
54
+ name: 'llui_network_tail',
55
+ description: 'Return recent network requests captured since the CDP session started. Includes URL, method, status, timing, and failure info.',
56
+ inputSchema: {
57
+ type: 'object',
58
+ properties: {
59
+ limit: {
60
+ type: 'number',
61
+ description: 'Max entries to return (default: all buffered, max 500)',
62
+ },
63
+ filter: {
64
+ type: 'object',
65
+ properties: {
66
+ urlPattern: { type: 'string', description: 'Regex pattern to match URLs' },
67
+ status: { type: 'number', description: 'HTTP status code to filter on' },
68
+ },
69
+ },
70
+ },
71
+ },
72
+ }, 'cdp', async (args, ctx) => {
73
+ if (!ctx.cdp)
74
+ return cdpUnavailable();
75
+ const filter = args.filter;
76
+ return { entries: ctx.cdp.getNetworkBuffer(args.limit, filter) };
77
+ });
78
+ registry.register({
79
+ name: 'llui_console_tail',
80
+ description: 'Return recent browser console entries (log, info, warn, error, debug) captured since the CDP session started.',
81
+ inputSchema: {
82
+ type: 'object',
83
+ properties: {
84
+ limit: { type: 'number', description: 'Max entries to return' },
85
+ level: {
86
+ type: 'string',
87
+ enum: ['log', 'info', 'warn', 'error', 'debug'],
88
+ description: 'Filter to this level only',
89
+ },
90
+ },
91
+ },
92
+ }, 'cdp', async (args, ctx) => {
93
+ if (!ctx.cdp)
94
+ return cdpUnavailable();
95
+ return {
96
+ entries: ctx.cdp.getConsoleBuffer(args.limit, args.level),
97
+ };
98
+ });
99
+ registry.register({
100
+ name: 'llui_uncaught_errors',
101
+ description: 'Return recent uncaught JavaScript exceptions captured since the CDP session started.',
102
+ inputSchema: {
103
+ type: 'object',
104
+ properties: {
105
+ limit: { type: 'number', description: 'Max entries to return' },
106
+ },
107
+ },
108
+ }, 'cdp', async (args, ctx) => {
109
+ if (!ctx.cdp)
110
+ return cdpUnavailable();
111
+ return { errors: ctx.cdp.getErrorBuffer(args.limit) };
112
+ });
113
+ registry.register({
114
+ name: 'llui_browser_close',
115
+ description: 'Close the Playwright-owned fallback browser and clear the CDP session buffers. No-op if the browser is user-owned (attached via :9222).',
116
+ inputSchema: { type: 'object', properties: {} },
117
+ }, 'cdp', async (_args, ctx) => {
118
+ if (!ctx.cdp)
119
+ return { closed: false, reason: 'no_cdp_transport' };
120
+ return ctx.cdp.closeBrowser();
121
+ });
122
+ }
123
+ function cdpUnavailable() {
124
+ return {
125
+ ok: false,
126
+ error: 'cdp_unavailable',
127
+ hint: 'CDP transport is not configured. Pass --url <devUrl> to llui-mcp.',
128
+ };
129
+ }
130
+ //# sourceMappingURL=cdp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cdp.js","sourceRoot":"","sources":["../../src/tools/cdp.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ToolRegistry } from '../tool-registry.js'\n\nexport function registerCdpTools(registry: ToolRegistry): void {\n registry.register(\n {\n name: 'llui_screenshot',\n description:\n 'Capture a screenshot of the browser page or a specific element. Returns base64-encoded PNG or JPEG. Requires CDP transport (browser attached via :9222 or Playwright fallback).',\n inputSchema: {\n type: 'object',\n properties: {\n selector: { type: 'string', description: 'CSS selector — screenshot only this element' },\n fullPage: {\n type: 'boolean',\n description: 'Capture full scrollable page (default false)',\n },\n format: {\n type: 'string',\n enum: ['png', 'jpeg'],\n description: 'Image format (default png)',\n },\n },\n },\n },\n 'cdp',\n async (args, ctx) => {\n if (!ctx.cdp) return cdpUnavailable()\n return ctx.cdp.screenshot({\n selector: args.selector as string | undefined,\n fullPage: args.fullPage as boolean | undefined,\n format: args.format as 'png' | 'jpeg' | undefined,\n })\n },\n )\n\n registry.register(\n {\n name: 'llui_a11y_tree',\n description:\n 'Return the accessibility tree for the page or a specific element. Useful for verifying ARIA roles, labels, and keyboard navigation structure.',\n inputSchema: {\n type: 'object',\n properties: {\n selector: {\n type: 'string',\n description: 'Root element CSS selector (default: full page)',\n },\n interestingOnly: {\n type: 'boolean',\n description: 'Omit nodes with no accessibility-relevant attributes (default true)',\n },\n },\n },\n },\n 'cdp',\n async (args, ctx) => {\n if (!ctx.cdp) return cdpUnavailable()\n return ctx.cdp.accessibilitySnapshot({\n selector: args.selector as string | undefined,\n interestingOnly: (args.interestingOnly as boolean | undefined) ?? true,\n })\n },\n )\n\n registry.register(\n {\n name: 'llui_network_tail',\n description:\n 'Return recent network requests captured since the CDP session started. Includes URL, method, status, timing, and failure info.',\n inputSchema: {\n type: 'object',\n properties: {\n limit: {\n type: 'number',\n description: 'Max entries to return (default: all buffered, max 500)',\n },\n filter: {\n type: 'object',\n properties: {\n urlPattern: { type: 'string', description: 'Regex pattern to match URLs' },\n status: { type: 'number', description: 'HTTP status code to filter on' },\n },\n },\n },\n },\n },\n 'cdp',\n async (args, ctx) => {\n if (!ctx.cdp) return cdpUnavailable()\n const filter = args.filter as { urlPattern?: string; status?: number } | undefined\n return { entries: ctx.cdp.getNetworkBuffer(args.limit as number | undefined, filter) }\n },\n )\n\n registry.register(\n {\n name: 'llui_console_tail',\n description:\n 'Return recent browser console entries (log, info, warn, error, debug) captured since the CDP session started.',\n inputSchema: {\n type: 'object',\n properties: {\n limit: { type: 'number', description: 'Max entries to return' },\n level: {\n type: 'string',\n enum: ['log', 'info', 'warn', 'error', 'debug'],\n description: 'Filter to this level only',\n },\n },\n },\n },\n 'cdp',\n async (args, ctx) => {\n if (!ctx.cdp) return cdpUnavailable()\n return {\n entries: ctx.cdp.getConsoleBuffer(\n args.limit as number | undefined,\n args.level as string | undefined,\n ),\n }\n },\n )\n\n registry.register(\n {\n name: 'llui_uncaught_errors',\n description:\n 'Return recent uncaught JavaScript exceptions captured since the CDP session started.',\n inputSchema: {\n type: 'object',\n properties: {\n limit: { type: 'number', description: 'Max entries to return' },\n },\n },\n },\n 'cdp',\n async (args, ctx) => {\n if (!ctx.cdp) return cdpUnavailable()\n return { errors: ctx.cdp.getErrorBuffer(args.limit as number | undefined) }\n },\n )\n\n registry.register(\n {\n name: 'llui_browser_close',\n description:\n 'Close the Playwright-owned fallback browser and clear the CDP session buffers. No-op if the browser is user-owned (attached via :9222).',\n inputSchema: { type: 'object', properties: {} },\n },\n 'cdp',\n async (_args, ctx) => {\n if (!ctx.cdp) return { closed: false, reason: 'no_cdp_transport' }\n return ctx.cdp.closeBrowser()\n },\n )\n}\n\nfunction cdpUnavailable() {\n return {\n ok: false,\n error: 'cdp_unavailable',\n hint: 'CDP transport is not configured. Pass --url <devUrl> to llui-mcp.',\n }\n}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { ToolRegistry } from '../tool-registry.js';
2
+ export declare function registerCompilerTools(registry: ToolRegistry): void;
3
+ //# sourceMappingURL=compiler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compiler.d.ts","sourceRoot":"","sources":["../../src/tools/compiler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAEvD,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CA8ElE"}