llui-agent 0.0.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Franco Ponticelli
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,34 @@
1
+ # llui-agent
2
+
3
+ MCP bridge for the [LLui Agent Protocol](../../docs/superpowers/specs/2026-04-19-llui-agent-design.md). Install once into your LLM client; paste a `/llui-connect <url> <token>` into any Claude conversation to bind it to a running LLui app.
4
+
5
+ ## Install (Claude Desktop)
6
+
7
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or equivalent on your OS:
8
+
9
+ ```json
10
+ {
11
+ "mcpServers": {
12
+ "llui": {
13
+ "command": "npx",
14
+ "args": ["-y", "llui-agent"]
15
+ }
16
+ }
17
+ }
18
+ ```
19
+
20
+ Restart Claude Desktop. The 11 LLui tools (`llui_connect_session`, `llui_disconnect_session`, `describe_app`, `get_state`, `list_actions`, `send_message`, `get_confirm_result`, `wait_for_change`, `query_dom`, `describe_visible_content`, `describe_context`) and the `/llui-connect` prompt now appear in Claude.
21
+
22
+ ## Use
23
+
24
+ Open any LLui app that's built with `@llui/agent/client`. Click "Connect with Claude" in the app. Copy the generated `/llui-connect <url> <token>` string into Claude. Claude will now talk to that specific app instance.
25
+
26
+ Each Claude chat is bound to ONE LLui app at a time. To switch, run `/llui-disconnect` or start a new chat.
27
+
28
+ ## How it works
29
+
30
+ 1. Your LLui app mints a per-browser-session token and shows a `/llui-connect` string.
31
+ 2. You paste into Claude — the bridge records `{url, token}` for this chat.
32
+ 3. The bridge pings `POST {url}/describe` to validate and cache the app's schema.
33
+ 4. Subsequent Claude tool calls (`get_state`, `send_message`, etc.) forward to `{url}/<path>` with your token as a Bearer.
34
+ 5. Sensitive actions (`@requiresConfirm` in the app's code) route through a confirmation prompt that only the user can approve.
@@ -0,0 +1,19 @@
1
+ import type { LapDescribeResponse } from '@llui/agent/protocol';
2
+ export type Binding = {
3
+ url: string;
4
+ token: string;
5
+ describe: LapDescribeResponse | null;
6
+ };
7
+ /**
8
+ * Per-MCP-session map. Keyed by the SDK's session id (one per Claude
9
+ * conversation). Spec §11.3.
10
+ */
11
+ export declare class BindingMap {
12
+ private map;
13
+ set(sessionId: string, url: string, token: string): void;
14
+ get(sessionId: string): Binding | null;
15
+ setDescribe(sessionId: string, describe: LapDescribeResponse): void;
16
+ clear(sessionId: string): void;
17
+ has(sessionId: string): boolean;
18
+ }
19
+ //# sourceMappingURL=binding.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAE/D,MAAM,MAAM,OAAO,GAAG;IACpB,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,mBAAmB,GAAG,IAAI,CAAA;CACrC,CAAA;AAED;;;GAGG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,GAAG,CAA6B;IAExC,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAGxD,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI;IAGtC,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAInE,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI;IAG9B,GAAG,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO;CAGhC"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Per-MCP-session map. Keyed by the SDK's session id (one per Claude
3
+ * conversation). Spec §11.3.
4
+ */
5
+ export class BindingMap {
6
+ map = new Map();
7
+ set(sessionId, url, token) {
8
+ this.map.set(sessionId, { url, token, describe: null });
9
+ }
10
+ get(sessionId) {
11
+ return this.map.get(sessionId) ?? null;
12
+ }
13
+ setDescribe(sessionId, describe) {
14
+ const b = this.map.get(sessionId);
15
+ if (b)
16
+ this.map.set(sessionId, { ...b, describe });
17
+ }
18
+ clear(sessionId) {
19
+ this.map.delete(sessionId);
20
+ }
21
+ has(sessionId) {
22
+ return this.map.has(sessionId);
23
+ }
24
+ }
25
+ //# sourceMappingURL=binding.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"binding.js","sourceRoot":"","sources":["../src/binding.ts"],"names":[],"mappings":"AAQA;;;GAGG;AACH,MAAM,OAAO,UAAU;IACb,GAAG,GAAG,IAAI,GAAG,EAAmB,CAAA;IAExC,GAAG,CAAC,SAAiB,EAAE,GAAW,EAAE,KAAa;QAC/C,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAA;IACzD,CAAC;IACD,GAAG,CAAC,SAAiB;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,IAAI,IAAI,CAAA;IACxC,CAAC;IACD,WAAW,CAAC,SAAiB,EAAE,QAA6B;QAC1D,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QACjC,IAAI,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,CAAC,CAAA;IACpD,CAAC;IACD,KAAK,CAAC,SAAiB;QACrB,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAA;IAC5B,CAAC;IACD,GAAG,CAAC,SAAiB;QACnB,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IAChC,CAAC;CACF","sourcesContent":["import type { LapDescribeResponse } from '@llui/agent/protocol'\n\nexport type Binding = {\n url: string // LAP base path, e.g. \"https://app/agent/lap/v1\"\n token: string\n describe: LapDescribeResponse | null // cached describe_app response; populated on bind\n}\n\n/**\n * Per-MCP-session map. Keyed by the SDK's session id (one per Claude\n * conversation). Spec §11.3.\n */\nexport class BindingMap {\n private map = new Map<string, Binding>()\n\n set(sessionId: string, url: string, token: string): void {\n this.map.set(sessionId, { url, token, describe: null })\n }\n get(sessionId: string): Binding | null {\n return this.map.get(sessionId) ?? null\n }\n setDescribe(sessionId: string, describe: LapDescribeResponse): void {\n const b = this.map.get(sessionId)\n if (b) this.map.set(sessionId, { ...b, describe })\n }\n clear(sessionId: string): void {\n this.map.delete(sessionId)\n }\n has(sessionId: string): boolean {\n return this.map.has(sessionId)\n }\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { BindingMap } from './binding.js';
3
+ export type BridgeDeps = {
4
+ /** Injectable for tests. */
5
+ fetch?: typeof fetch;
6
+ /** MCP session ID for this client. In stdio mode there's one session; derive from the Server instance. */
7
+ sessionId: string;
8
+ /** Shared binding map (one BindingMap per process). */
9
+ bindings: BindingMap;
10
+ /** Package version — set from package.json at boot. */
11
+ version: string;
12
+ };
13
+ export declare function createBridgeServer(deps: BridgeDeps): McpServer;
14
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAQ/E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAKzC,MAAM,MAAM,UAAU,GAAG;IACvB,4BAA4B;IAC5B,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;IACpB,0GAA0G;IAC1G,SAAS,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,QAAQ,EAAE,UAAU,CAAA;IACpB,uDAAuD;IACvD,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CAwE9D"}
package/dist/bridge.js ADDED
@@ -0,0 +1,74 @@
1
+ import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
+ import { TOOLS, TOOL_TO_LAP_PATH } from './tools.js';
4
+ import { BindingMap } from './binding.js';
5
+ import { forwardLap } from './forwarder.js';
6
+ import { registerPrompts } from './prompts.js';
7
+ export function createBridgeServer(deps) {
8
+ const server = new McpServer({ name: 'llui-agent', version: deps.version }, { capabilities: { tools: {}, prompts: {} } });
9
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
10
+ tools: TOOLS,
11
+ }));
12
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
13
+ const { name, arguments: args = {} } = req.params;
14
+ if (name === 'llui_connect_session') {
15
+ const { url, token } = args;
16
+ if (typeof url !== 'string' || typeof token !== 'string') {
17
+ return errorResult('invalid: url and token required');
18
+ }
19
+ deps.bindings.set(deps.sessionId, url, token);
20
+ // Validate immediately by pinging /describe
21
+ const res = await forwardLap(url, token, '/describe', {}, { fetch: deps.fetch });
22
+ if (!res.ok) {
23
+ deps.bindings.clear(deps.sessionId);
24
+ return errorResult(`connect failed: ${JSON.stringify(res.error)}`);
25
+ }
26
+ const describe = res.body;
27
+ deps.bindings.setDescribe(deps.sessionId, describe);
28
+ return okResult({
29
+ appName: describe.name,
30
+ appVersion: describe.version,
31
+ status: 'connected',
32
+ });
33
+ }
34
+ if (name === 'llui_disconnect_session') {
35
+ deps.bindings.clear(deps.sessionId);
36
+ return okResult({ status: 'disconnected' });
37
+ }
38
+ // Forwarded tools
39
+ const binding = deps.bindings.get(deps.sessionId);
40
+ if (!binding) {
41
+ return errorResult('not bound — ask the user to run /llui-connect <url> <token> first');
42
+ }
43
+ // describe_app can serve from cache
44
+ if (name === 'describe_app' && binding.describe) {
45
+ return okResult(binding.describe);
46
+ }
47
+ const lapPath = TOOL_TO_LAP_PATH[name];
48
+ if (!lapPath)
49
+ return errorResult(`unknown tool: ${name}`);
50
+ const res = await forwardLap(binding.url, binding.token, lapPath, args, { fetch: deps.fetch });
51
+ if (!res.ok) {
52
+ return errorResult(`LAP ${lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`);
53
+ }
54
+ // Cache describe_app responses after the first call too
55
+ if (name === 'describe_app') {
56
+ deps.bindings.setDescribe(deps.sessionId, res.body);
57
+ }
58
+ return okResult(res.body);
59
+ });
60
+ registerPrompts(server);
61
+ return server;
62
+ }
63
+ function okResult(body) {
64
+ return {
65
+ content: [{ type: 'text', text: JSON.stringify(body) }],
66
+ };
67
+ }
68
+ function errorResult(msg) {
69
+ return {
70
+ content: [{ type: 'text', text: msg }],
71
+ isError: true,
72
+ };
73
+ }
74
+ //# sourceMappingURL=bridge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js'\nimport {\n CallToolRequestSchema,\n ListToolsRequestSchema,\n type CallToolResult,\n type ListToolsResult,\n} from '@modelcontextprotocol/sdk/types.js'\nimport { TOOLS, TOOL_TO_LAP_PATH } from './tools.js'\nimport { BindingMap } from './binding.js'\nimport { forwardLap } from './forwarder.js'\nimport type { LapDescribeResponse } from '@llui/agent/protocol'\nimport { registerPrompts } from './prompts.js'\n\nexport type BridgeDeps = {\n /** Injectable for tests. */\n fetch?: typeof fetch\n /** MCP session ID for this client. In stdio mode there's one session; derive from the Server instance. */\n sessionId: string\n /** Shared binding map (one BindingMap per process). */\n bindings: BindingMap\n /** Package version — set from package.json at boot. */\n version: string\n}\n\nexport function createBridgeServer(deps: BridgeDeps): McpServer {\n const server = new McpServer(\n { name: 'llui-agent', version: deps.version },\n { capabilities: { tools: {}, prompts: {} } },\n )\n\n server.setRequestHandler(\n ListToolsRequestSchema,\n async (): Promise<ListToolsResult> => ({\n tools: TOOLS,\n }),\n )\n\n server.setRequestHandler(CallToolRequestSchema, async (req): Promise<CallToolResult> => {\n const { name, arguments: args = {} } = req.params\n\n if (name === 'llui_connect_session') {\n const { url, token } = args as { url?: string; token?: string }\n if (typeof url !== 'string' || typeof token !== 'string') {\n return errorResult('invalid: url and token required')\n }\n deps.bindings.set(deps.sessionId, url, token)\n // Validate immediately by pinging /describe\n const res = await forwardLap(url, token, '/describe', {}, { fetch: deps.fetch })\n if (!res.ok) {\n deps.bindings.clear(deps.sessionId)\n return errorResult(`connect failed: ${JSON.stringify(res.error)}`)\n }\n const describe = res.body as LapDescribeResponse\n deps.bindings.setDescribe(deps.sessionId, describe)\n return okResult({\n appName: describe.name,\n appVersion: describe.version,\n status: 'connected',\n })\n }\n\n if (name === 'llui_disconnect_session') {\n deps.bindings.clear(deps.sessionId)\n return okResult({ status: 'disconnected' })\n }\n\n // Forwarded tools\n const binding = deps.bindings.get(deps.sessionId)\n if (!binding) {\n return errorResult('not bound — ask the user to run /llui-connect <url> <token> first')\n }\n\n // describe_app can serve from cache\n if (name === 'describe_app' && binding.describe) {\n return okResult(binding.describe)\n }\n\n const lapPath = TOOL_TO_LAP_PATH[name]\n if (!lapPath) return errorResult(`unknown tool: ${name}`)\n\n const res = await forwardLap(binding.url, binding.token, lapPath, args, { fetch: deps.fetch })\n if (!res.ok) {\n return errorResult(`LAP ${lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`)\n }\n\n // Cache describe_app responses after the first call too\n if (name === 'describe_app') {\n deps.bindings.setDescribe(deps.sessionId, res.body as LapDescribeResponse)\n }\n\n return okResult(res.body)\n })\n\n registerPrompts(server)\n\n return server\n}\n\nfunction okResult(body: unknown): CallToolResult {\n return {\n content: [{ type: 'text', text: JSON.stringify(body) }],\n }\n}\n\nfunction errorResult(msg: string): CallToolResult {\n return {\n content: [{ type: 'text', text: msg }],\n isError: true,\n }\n}\n"]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { readFileSync } from 'node:fs';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { createBridgeServer } from './bridge.js';
7
+ import { BindingMap } from './binding.js';
8
+ const PACKAGE_VERSION = (() => {
9
+ try {
10
+ const here = dirname(fileURLToPath(import.meta.url));
11
+ const pkg = JSON.parse(readFileSync(resolve(here, '../package.json'), 'utf8'));
12
+ return typeof pkg.version === 'string' ? pkg.version : 'unknown';
13
+ }
14
+ catch {
15
+ return 'unknown';
16
+ }
17
+ })();
18
+ async function main() {
19
+ const bindings = new BindingMap();
20
+ // Stdio is one session per process. Use a fixed session id.
21
+ const sessionId = 'stdio';
22
+ const server = createBridgeServer({ sessionId, bindings, version: PACKAGE_VERSION });
23
+ const transport = new StdioServerTransport();
24
+ await server.connect(transport);
25
+ }
26
+ main().catch((err) => {
27
+ console.error('[llui-agent] fatal:', err);
28
+ process.exit(1);
29
+ });
30
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAChF,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAC5C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AACxC,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE;IAC5B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE,MAAM,CAAC,CAE5E,CAAA;QACD,OAAO,OAAO,GAAG,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAA;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAA;IAClB,CAAC;AACH,CAAC,CAAC,EAAE,CAAA;AAEJ,KAAK,UAAU,IAAI;IACjB,MAAM,QAAQ,GAAG,IAAI,UAAU,EAAE,CAAA;IACjC,4DAA4D;IAC5D,MAAM,SAAS,GAAG,OAAO,CAAA;IACzB,MAAM,MAAM,GAAG,kBAAkB,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE,CAAC,CAAA;IACpF,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAA;IAC5C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;AACjC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,qBAAqB,EAAE,GAAG,CAAC,CAAA;IACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AACjB,CAAC,CAAC,CAAA","sourcesContent":["#!/usr/bin/env node\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'\nimport { readFileSync } from 'node:fs'\nimport { dirname, resolve } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { createBridgeServer } from './bridge.js'\nimport { BindingMap } from './binding.js'\n\nconst PACKAGE_VERSION = (() => {\n try {\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\nasync function main(): Promise<void> {\n const bindings = new BindingMap()\n // Stdio is one session per process. Use a fixed session id.\n const sessionId = 'stdio'\n const server = createBridgeServer({ sessionId, bindings, version: PACKAGE_VERSION })\n const transport = new StdioServerTransport()\n await server.connect(transport)\n}\n\nmain().catch((err) => {\n console.error('[llui-agent] fatal:', err)\n process.exit(1)\n})\n"]}
@@ -0,0 +1,18 @@
1
+ export type ForwardResult = {
2
+ ok: true;
3
+ body: unknown;
4
+ } | {
5
+ ok: false;
6
+ status: number;
7
+ error: unknown;
8
+ };
9
+ export type ForwardDeps = {
10
+ fetch?: typeof fetch;
11
+ };
12
+ /**
13
+ * POST {baseUrl}{path} with Authorization: Bearer {token}, JSON body.
14
+ * Returns a discriminated success/failure envelope.
15
+ * Spec §11.4.
16
+ */
17
+ export declare function forwardLap(baseUrl: string, token: string, path: string, args: object, deps?: ForwardDeps): Promise<ForwardResult>;
18
+ //# sourceMappingURL=forwarder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.d.ts","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAC3B;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAEjD,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAA;CACrB,CAAA;AAED;;;;GAIG;AACH,wBAAsB,UAAU,CAC9B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,aAAa,CAAC,CAuBxB"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * POST {baseUrl}{path} with Authorization: Bearer {token}, JSON body.
3
+ * Returns a discriminated success/failure envelope.
4
+ * Spec §11.4.
5
+ */
6
+ export async function forwardLap(baseUrl, token, path, args, deps = {}) {
7
+ const doFetch = deps.fetch ?? fetch.bind(globalThis);
8
+ const url = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) + path : baseUrl + path;
9
+ try {
10
+ const res = await doFetch(url, {
11
+ method: 'POST',
12
+ headers: {
13
+ 'content-type': 'application/json',
14
+ authorization: `Bearer ${token}`,
15
+ },
16
+ body: JSON.stringify(args),
17
+ });
18
+ let body = null;
19
+ try {
20
+ body = await res.json();
21
+ }
22
+ catch {
23
+ body = null;
24
+ }
25
+ if (!res.ok)
26
+ return { ok: false, status: res.status, error: body };
27
+ return { ok: true, body };
28
+ }
29
+ catch (e) {
30
+ return { ok: false, status: 0, error: { code: 'network', detail: String(e) } };
31
+ }
32
+ }
33
+ //# sourceMappingURL=forwarder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"forwarder.js","sourceRoot":"","sources":["../src/forwarder.ts"],"names":[],"mappings":"AAQA;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,OAAe,EACf,KAAa,EACb,IAAY,EACZ,IAAY,EACZ,OAAoB,EAAE;IAEtB,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACpD,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,GAAG,IAAI,CAAA;IAChF,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE;YAC7B,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,aAAa,EAAE,UAAU,KAAK,EAAE;aACjC;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAA;QACF,IAAI,IAAI,GAAY,IAAI,CAAA;QACxB,IAAI,CAAC;YACH,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAA;QACzB,CAAC;QAAC,MAAM,CAAC;YACP,IAAI,GAAG,IAAI,CAAA;QACb,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,CAAA;QAClE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC3B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAA;IAChF,CAAC;AACH,CAAC","sourcesContent":["export type ForwardResult =\n | { ok: true; body: unknown }\n | { ok: false; status: number; error: unknown }\n\nexport type ForwardDeps = {\n fetch?: typeof fetch\n}\n\n/**\n * POST {baseUrl}{path} with Authorization: Bearer {token}, JSON body.\n * Returns a discriminated success/failure envelope.\n * Spec §11.4.\n */\nexport async function forwardLap(\n baseUrl: string,\n token: string,\n path: string,\n args: object,\n deps: ForwardDeps = {},\n): Promise<ForwardResult> {\n const doFetch = deps.fetch ?? fetch.bind(globalThis)\n const url = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) + path : baseUrl + path\n try {\n const res = await doFetch(url, {\n method: 'POST',\n headers: {\n 'content-type': 'application/json',\n authorization: `Bearer ${token}`,\n },\n body: JSON.stringify(args),\n })\n let body: unknown = null\n try {\n body = await res.json()\n } catch {\n body = null\n }\n if (!res.ok) return { ok: false, status: res.status, error: body }\n return { ok: true, body }\n } catch (e) {\n return { ok: false, status: 0, error: { code: 'network', detail: String(e) } }\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ // Intentionally empty — bridge is invoked via CLI, not imported as a library.
2
+ export {};
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,OAAO,EAAE,CAAA","sourcesContent":["// Intentionally empty — bridge is invoked via CLI, not imported as a library.\nexport {}\n"]}
@@ -0,0 +1,3 @@
1
+ import type { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js';
2
+ export declare function registerPrompts(server: McpServer): void;
3
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,SAAS,EAAE,MAAM,2CAA2C,CAAA;AAQpF,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAuCvD"}
@@ -0,0 +1,36 @@
1
+ import { GetPromptRequestSchema, ListPromptsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
2
+ export function registerPrompts(server) {
3
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
4
+ prompts: [
5
+ {
6
+ name: 'llui-connect',
7
+ description: 'Bind this Claude conversation to an LLui app. Paste the URL and token the app showed you.',
8
+ arguments: [
9
+ { name: 'url', description: 'LAP base URL', required: true },
10
+ { name: 'token', description: 'Bearer token', required: true },
11
+ ],
12
+ },
13
+ ],
14
+ }));
15
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
16
+ if (req.params.name !== 'llui-connect') {
17
+ throw new Error(`unknown prompt: ${req.params.name}`);
18
+ }
19
+ const url = req.params.arguments?.['url'] ?? '';
20
+ const token = req.params.arguments?.['token'] ?? '';
21
+ return {
22
+ description: `Bind to LLui app at ${url}`,
23
+ messages: [
24
+ {
25
+ role: 'user',
26
+ content: {
27
+ type: 'text',
28
+ text: `Please connect this conversation to the LLui app at ${url}. ` +
29
+ `Call llui_connect_session with url=${JSON.stringify(url)} and token=${JSON.stringify(token)}.`,
30
+ },
31
+ },
32
+ ],
33
+ };
34
+ });
35
+ }
36
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EACL,sBAAsB,EACtB,wBAAwB,GAGzB,MAAM,oCAAoC,CAAA;AAE3C,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,iBAAiB,CACtB,wBAAwB,EACxB,KAAK,IAAgC,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,cAAc;gBACpB,WAAW,EACT,2FAA2F;gBAC7F,SAAS,EAAE;oBACT,EAAE,IAAI,EAAE,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE;oBAC5D,EAAE,IAAI,EAAE,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,QAAQ,EAAE,IAAI,EAAE;iBAC/D;aACF;SACF;KACF,CAAC,CACH,CAAA;IAED,MAAM,CAAC,iBAAiB,CAAC,sBAAsB,EAAE,KAAK,EAAE,GAAG,EAA4B,EAAE;QACvF,IAAI,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,cAAc,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CAAC,mBAAmB,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAA;QACvD,CAAC;QACD,MAAM,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAA;QAC/C,MAAM,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,CAAA;QACnD,OAAO;YACL,WAAW,EAAE,uBAAuB,GAAG,EAAE;YACzC,QAAQ,EAAE;gBACR;oBACE,IAAI,EAAE,MAAM;oBACZ,OAAO,EAAE;wBACP,IAAI,EAAE,MAAM;wBACZ,IAAI,EACF,uDAAuD,GAAG,IAAI;4BAC9D,sCAAsC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG;qBAClG;iBACF;aACF;SACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import type { Server as McpServer } from '@modelcontextprotocol/sdk/server/index.js'\nimport {\n GetPromptRequestSchema,\n ListPromptsRequestSchema,\n type ListPromptsResult,\n type GetPromptResult,\n} from '@modelcontextprotocol/sdk/types.js'\n\nexport function registerPrompts(server: McpServer): void {\n server.setRequestHandler(\n ListPromptsRequestSchema,\n async (): Promise<ListPromptsResult> => ({\n prompts: [\n {\n name: 'llui-connect',\n description:\n 'Bind this Claude conversation to an LLui app. Paste the URL and token the app showed you.',\n arguments: [\n { name: 'url', description: 'LAP base URL', required: true },\n { name: 'token', description: 'Bearer token', required: true },\n ],\n },\n ],\n }),\n )\n\n server.setRequestHandler(GetPromptRequestSchema, async (req): Promise<GetPromptResult> => {\n if (req.params.name !== 'llui-connect') {\n throw new Error(`unknown prompt: ${req.params.name}`)\n }\n const url = req.params.arguments?.['url'] ?? ''\n const token = req.params.arguments?.['token'] ?? ''\n return {\n description: `Bind to LLui app at ${url}`,\n messages: [\n {\n role: 'user',\n content: {\n type: 'text',\n text:\n `Please connect this conversation to the LLui app at ${url}. ` +\n `Call llui_connect_session with url=${JSON.stringify(url)} and token=${JSON.stringify(token)}.`,\n },\n },\n ],\n }\n })\n}\n"]}
@@ -0,0 +1,14 @@
1
+ import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js';
2
+ /**
3
+ * The 10 MCP tools Claude sees:
4
+ * 2 meta-tools (bind/unbind) + 8 forwarded tools (1:1 with LAP endpoints).
5
+ *
6
+ * Spec §8.
7
+ */
8
+ export declare const TOOLS: ListToolsResult['tools'];
9
+ /**
10
+ * Mapping from tool name → LAP path for the forwarded subset.
11
+ * Meta-tools handled separately in bridge.ts.
12
+ */
13
+ export declare const TOOL_TO_LAP_PATH: Record<string, string>;
14
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oCAAoC,CAAA;AAEzE;;;;;GAKG;AACH,eAAO,MAAM,KAAK,EAAE,eAAe,CAAC,OAAO,CAkH1C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAUnD,CAAA"}
package/dist/tools.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * The 10 MCP tools Claude sees:
3
+ * 2 meta-tools (bind/unbind) + 8 forwarded tools (1:1 with LAP endpoints).
4
+ *
5
+ * Spec §8.
6
+ */
7
+ export const TOOLS = [
8
+ {
9
+ name: 'llui_connect_session',
10
+ description: 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes /llui-connect <url> <token>. Subsequent LLui tool calls target the bound app.',
11
+ inputSchema: {
12
+ type: 'object',
13
+ properties: {
14
+ url: {
15
+ type: 'string',
16
+ description: 'LAP base URL (e.g. https://app.example/agent/lap/v1)',
17
+ },
18
+ token: { type: 'string', description: 'Bearer token for LAP calls' },
19
+ },
20
+ required: ['url', 'token'],
21
+ },
22
+ },
23
+ {
24
+ name: 'llui_disconnect_session',
25
+ description: 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',
26
+ inputSchema: { type: 'object', properties: {} },
27
+ },
28
+ {
29
+ name: 'describe_app',
30
+ description: "Return the bound app's name, version, state/message schemas, annotations, and static docs.",
31
+ inputSchema: { type: 'object', properties: {} },
32
+ },
33
+ {
34
+ name: 'get_state',
35
+ description: 'Return the current app state. Optional `path` (JSON-pointer) to narrow the slice.',
36
+ inputSchema: {
37
+ type: 'object',
38
+ properties: {
39
+ path: { type: 'string', description: 'Optional JSON-pointer, e.g. "/user/name"' },
40
+ },
41
+ },
42
+ },
43
+ {
44
+ name: 'list_actions',
45
+ description: 'Return the currently-affordable actions: visible UI bindings plus agent-affordable registry entries, filtered by annotation gates.',
46
+ inputSchema: { type: 'object', properties: {} },
47
+ },
48
+ {
49
+ name: 'send_message',
50
+ description: 'Dispatch a message to the app. Auto-proposes a user confirmation when the message variant is @requiresConfirm. Returns dispatched / pending-confirmation / rejected.',
51
+ inputSchema: {
52
+ type: 'object',
53
+ properties: {
54
+ msg: { type: 'object', description: 'The message to dispatch; must have a `type` string' },
55
+ reason: {
56
+ type: 'string',
57
+ description: 'User-facing rationale (required for confirm-gated variants)',
58
+ },
59
+ waitFor: { type: 'string', enum: ['idle', 'none'], description: 'default "idle"' },
60
+ timeoutMs: { type: 'number' },
61
+ },
62
+ required: ['msg'],
63
+ },
64
+ },
65
+ {
66
+ name: 'get_confirm_result',
67
+ description: 'Poll a pending-confirmation by confirmId. Returns confirmed / rejected / still-pending.',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ confirmId: { type: 'string' },
72
+ timeoutMs: { type: 'number' },
73
+ },
74
+ required: ['confirmId'],
75
+ },
76
+ },
77
+ {
78
+ name: 'wait_for_change',
79
+ description: 'Long-poll for a state change. Returns changed / timeout.',
80
+ inputSchema: {
81
+ type: 'object',
82
+ properties: {
83
+ path: {
84
+ type: 'string',
85
+ description: 'Optional JSON-pointer to narrow which state changes trigger resolution',
86
+ },
87
+ timeoutMs: { type: 'number' },
88
+ },
89
+ },
90
+ },
91
+ {
92
+ name: 'query_dom',
93
+ description: 'Read elements tagged with data-agent="<name>" in the rendered UI.',
94
+ inputSchema: {
95
+ type: 'object',
96
+ properties: {
97
+ name: { type: 'string' },
98
+ multiple: { type: 'boolean' },
99
+ },
100
+ required: ['name'],
101
+ },
102
+ },
103
+ {
104
+ name: 'describe_visible_content',
105
+ description: 'Return a structured outline of the currently-visible data-agent-tagged subtrees.',
106
+ inputSchema: { type: 'object', properties: {} },
107
+ },
108
+ {
109
+ name: 'describe_context',
110
+ description: 'Return the current per-state narrative docs (agentContext) — what the user is trying to do right now.',
111
+ inputSchema: { type: 'object', properties: {} },
112
+ },
113
+ ];
114
+ /**
115
+ * Mapping from tool name → LAP path for the forwarded subset.
116
+ * Meta-tools handled separately in bridge.ts.
117
+ */
118
+ export const TOOL_TO_LAP_PATH = {
119
+ describe_app: '/describe',
120
+ get_state: '/state',
121
+ list_actions: '/actions',
122
+ send_message: '/message',
123
+ get_confirm_result: '/confirm-result',
124
+ wait_for_change: '/wait',
125
+ query_dom: '/query-dom',
126
+ describe_visible_content: '/describe-visible',
127
+ describe_context: '/context',
128
+ };
129
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js'\n\n/**\n * The 10 MCP tools Claude sees:\n * 2 meta-tools (bind/unbind) + 8 forwarded tools (1:1 with LAP endpoints).\n *\n * Spec §8.\n */\nexport const TOOLS: ListToolsResult['tools'] = [\n {\n name: 'llui_connect_session',\n description:\n 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes /llui-connect <url> <token>. Subsequent LLui tool calls target the bound app.',\n inputSchema: {\n type: 'object',\n properties: {\n url: {\n type: 'string',\n description: 'LAP base URL (e.g. https://app.example/agent/lap/v1)',\n },\n token: { type: 'string', description: 'Bearer token for LAP calls' },\n },\n required: ['url', 'token'],\n },\n },\n {\n name: 'llui_disconnect_session',\n description:\n 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',\n inputSchema: { type: 'object', properties: {} },\n },\n {\n name: 'describe_app',\n description:\n \"Return the bound app's name, version, state/message schemas, annotations, and static docs.\",\n inputSchema: { type: 'object', properties: {} },\n },\n {\n name: 'get_state',\n description:\n 'Return the current app state. Optional `path` (JSON-pointer) to narrow the slice.',\n inputSchema: {\n type: 'object',\n properties: {\n path: { type: 'string', description: 'Optional JSON-pointer, e.g. \"/user/name\"' },\n },\n },\n },\n {\n name: 'list_actions',\n description:\n 'Return the currently-affordable actions: visible UI bindings plus agent-affordable registry entries, filtered by annotation gates.',\n inputSchema: { type: 'object', properties: {} },\n },\n {\n name: 'send_message',\n description:\n 'Dispatch a message to the app. Auto-proposes a user confirmation when the message variant is @requiresConfirm. Returns dispatched / pending-confirmation / rejected.',\n inputSchema: {\n type: 'object',\n properties: {\n msg: { type: 'object', description: 'The message to dispatch; must have a `type` string' },\n reason: {\n type: 'string',\n description: 'User-facing rationale (required for confirm-gated variants)',\n },\n waitFor: { type: 'string', enum: ['idle', 'none'], description: 'default \"idle\"' },\n timeoutMs: { type: 'number' },\n },\n required: ['msg'],\n },\n },\n {\n name: 'get_confirm_result',\n description:\n 'Poll a pending-confirmation by confirmId. Returns confirmed / rejected / still-pending.',\n inputSchema: {\n type: 'object',\n properties: {\n confirmId: { type: 'string' },\n timeoutMs: { type: 'number' },\n },\n required: ['confirmId'],\n },\n },\n {\n name: 'wait_for_change',\n description: 'Long-poll for a state change. Returns changed / timeout.',\n inputSchema: {\n type: 'object',\n properties: {\n path: {\n type: 'string',\n description: 'Optional JSON-pointer to narrow which state changes trigger resolution',\n },\n timeoutMs: { type: 'number' },\n },\n },\n },\n {\n name: 'query_dom',\n description: 'Read elements tagged with data-agent=\"<name>\" in the rendered UI.',\n inputSchema: {\n type: 'object',\n properties: {\n name: { type: 'string' },\n multiple: { type: 'boolean' },\n },\n required: ['name'],\n },\n },\n {\n name: 'describe_visible_content',\n description: 'Return a structured outline of the currently-visible data-agent-tagged subtrees.',\n inputSchema: { type: 'object', properties: {} },\n },\n {\n name: 'describe_context',\n description:\n 'Return the current per-state narrative docs (agentContext) — what the user is trying to do right now.',\n inputSchema: { type: 'object', properties: {} },\n },\n]\n\n/**\n * Mapping from tool name → LAP path for the forwarded subset.\n * Meta-tools handled separately in bridge.ts.\n */\nexport const TOOL_TO_LAP_PATH: Record<string, string> = {\n describe_app: '/describe',\n get_state: '/state',\n list_actions: '/actions',\n send_message: '/message',\n get_confirm_result: '/confirm-result',\n wait_for_change: '/wait',\n query_dom: '/query-dom',\n describe_visible_content: '/describe-visible',\n describe_context: '/context',\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "llui-agent",
3
+ "version": "0.0.1",
4
+ "type": "module",
5
+ "bin": {
6
+ "llui-agent": "./dist/cli.js"
7
+ },
8
+ "exports": {
9
+ "./internal/bridge": {
10
+ "types": "./dist/bridge.d.ts",
11
+ "import": "./dist/bridge.js"
12
+ },
13
+ "./internal/binding": {
14
+ "types": "./dist/binding.d.ts",
15
+ "import": "./dist/binding.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "dependencies": {
22
+ "@modelcontextprotocol/sdk": "^1.29.0",
23
+ "@llui/agent": "0.0.29"
24
+ },
25
+ "devDependencies": {
26
+ "@types/node": "^22.0.0"
27
+ },
28
+ "description": "LLui Agent bridge — MCP server that translates Claude Desktop tool calls to LLui apps via LAP",
29
+ "keywords": [
30
+ "llui",
31
+ "agent",
32
+ "mcp",
33
+ "claude",
34
+ "bridge"
35
+ ],
36
+ "author": "Franco Ponticelli <franco.ponticelli@gmail.com>",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/fponticelli/llui.git",
41
+ "directory": "packages/agent-bridge"
42
+ },
43
+ "bugs": {
44
+ "url": "https://github.com/fponticelli/llui/issues"
45
+ },
46
+ "homepage": "https://github.com/fponticelli/llui/tree/main/packages/agent-bridge#readme",
47
+ "scripts": {
48
+ "build": "tsc -p tsconfig.build.json",
49
+ "check": "tsc --noEmit",
50
+ "lint": "eslint src",
51
+ "test": "vitest run"
52
+ }
53
+ }