llui-agent 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +46 -6
- package/dist/bridge.d.ts +14 -1
- package/dist/bridge.d.ts.map +1 -1
- package/dist/bridge.js +88 -45
- package/dist/bridge.js.map +1 -1
- package/dist/prompts.d.ts +9 -1
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +24 -29
- package/dist/prompts.js.map +1 -1
- package/dist/tools.d.ts +20 -26
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +133 -105
- package/dist/tools.js.map +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# llui-agent
|
|
2
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
|
|
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 the connect snippet from any LLui app to bind the conversation to that app.
|
|
4
4
|
|
|
5
5
|
## Install (Claude Desktop)
|
|
6
6
|
|
|
@@ -17,18 +17,58 @@ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) o
|
|
|
17
17
|
}
|
|
18
18
|
```
|
|
19
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`)
|
|
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`) now appear. Desktop exposes the bundled `llui-connect` MCP prompt as a slash command — see "Slash shortcuts" below.
|
|
21
|
+
|
|
22
|
+
## Install (Claude Code CLI)
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
claude mcp add --transport stdio llui -- npx -y llui-agent
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
For local development against unpublished bridge code, point at the built CLI instead:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
claude mcp add --transport stdio llui -- node /absolute/path/to/llui/packages/agent-bridge/dist/cli.js
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Run `/mcp` inside CC to confirm the server connected (or start a new session). The same 11 tools become available.
|
|
35
|
+
|
|
36
|
+
> **If you run CC in auto mode** (`permissions.defaultMode: "auto"` in `~/.claude/settings.json`), the auto-classifier silently rejects unrecognized MCP tools the first time they're called — Claude reports "tool was rejected" but no UI prompt is shown. Add the bridge's tools to your allowlist once so subsequent calls go through:
|
|
37
|
+
>
|
|
38
|
+
> ```jsonc
|
|
39
|
+
> // ~/.claude/settings.json
|
|
40
|
+
> {
|
|
41
|
+
> "permissions": {
|
|
42
|
+
> "allow": [
|
|
43
|
+
> "mcp__llui__*", // replace `llui` with the name you used in `claude mcp add`
|
|
44
|
+
> ],
|
|
45
|
+
> },
|
|
46
|
+
> }
|
|
47
|
+
> ```
|
|
48
|
+
>
|
|
49
|
+
> Users on `defaultMode: "default"` or `"ask"` instead get a permission prompt on the first call and don't need this allowlist entry.
|
|
21
50
|
|
|
22
51
|
## Use
|
|
23
52
|
|
|
24
|
-
Open any LLui app
|
|
53
|
+
Open any LLui app built with `@llui/agent/client`. Click "Connect with Claude" in the app and copy the generated snippet. Paste it into Claude — the snippet is a natural-language instruction containing the URL and token. Claude reads it and calls `llui_connect_session` to bind. The same snippet works in Desktop and CC.
|
|
54
|
+
|
|
55
|
+
Each Claude chat is bound to ONE LLui app at a time. To switch, ask Claude to call `llui_disconnect_session` and paste a new snippet.
|
|
56
|
+
|
|
57
|
+
## Slash shortcuts (optional)
|
|
58
|
+
|
|
59
|
+
The bridge registers an MCP prompt named `llui-connect`. Both clients surface it as a slash command, but the namespacing differs:
|
|
60
|
+
|
|
61
|
+
| Client | Shortcut |
|
|
62
|
+
| --------------- | ------------------------------------------------- |
|
|
63
|
+
| Claude Desktop | `/llui-connect <url> <token>` |
|
|
64
|
+
| Claude Code CLI | `/mcp__<server-name>__llui-connect <url> <token>` |
|
|
25
65
|
|
|
26
|
-
|
|
66
|
+
The `<server-name>` in CC is whatever you passed to `claude mcp add` — `llui` if you used the command above. Power-user shortcut only; the natural-language snippet from the app works the same in either client and doesn't depend on the server-name choice.
|
|
27
67
|
|
|
28
68
|
## How it works
|
|
29
69
|
|
|
30
|
-
1. Your LLui app mints a per-browser-session token and
|
|
31
|
-
2. You paste into Claude — the bridge records `{url, token}` for this chat.
|
|
70
|
+
1. Your LLui app mints a per-browser-session token and renders a connect snippet — a one-line instruction containing the LAP URL and the bearer token.
|
|
71
|
+
2. You paste into Claude — Claude reads the snippet, calls `llui_connect_session`, and the bridge records `{url, token}` for this chat.
|
|
32
72
|
3. The bridge pings `POST {url}/describe` to validate and cache the app's schema.
|
|
33
73
|
4. Subsequent Claude tool calls (`get_state`, `send_message`, etc.) forward to `{url}/<path>` with your token as a Bearer.
|
|
34
74
|
5. Sensitive actions (`@requiresConfirm` in the app's code) route through a confirmation prompt that only the user can approve.
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { BindingMap } from './binding.js';
|
|
3
3
|
export type BridgeDeps = {
|
|
4
4
|
/** Injectable for tests. */
|
|
@@ -10,5 +10,18 @@ export type BridgeDeps = {
|
|
|
10
10
|
/** Package version — set from package.json at boot. */
|
|
11
11
|
version: string;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Builds the bridge's MCP server using the high-level `McpServer`
|
|
15
|
+
* registrars. Each tool's Zod schema (declared once in `tools.ts`)
|
|
16
|
+
* drives both runtime input validation and the JSON Schema published
|
|
17
|
+
* to `tools/list` — eliminating the hand-written-schema-vs-handler
|
|
18
|
+
* drift that the low-level `setRequestHandler` pattern is prone to.
|
|
19
|
+
*
|
|
20
|
+
* Forwarded tools (`kind: 'forward'`) share a generic forwarder that
|
|
21
|
+
* looks up the binding, dispatches to LAP, and caches description
|
|
22
|
+
* payloads where applicable. The two meta tools
|
|
23
|
+
* (`llui_connect_session`, `llui_disconnect_session`) carry custom
|
|
24
|
+
* handlers that mutate the BindingMap directly.
|
|
25
|
+
*/
|
|
13
26
|
export declare function createBridgeServer(deps: BridgeDeps): McpServer;
|
|
14
27
|
//# sourceMappingURL=bridge.d.ts.map
|
package/dist/bridge.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGnE,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;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,UAAU,GAAG,SAAS,CAa9D"}
|
package/dist/bridge.js
CHANGED
|
@@ -1,75 +1,118 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { TOOLS, TOOL_TO_LAP_PATH } from './tools.js';
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { TOOL_DESCRIPTORS } from './tools.js';
|
|
4
3
|
import { BindingMap } from './binding.js';
|
|
5
4
|
import { forwardLap } from './forwarder.js';
|
|
6
5
|
import { registerPrompts } from './prompts.js';
|
|
6
|
+
/**
|
|
7
|
+
* Builds the bridge's MCP server using the high-level `McpServer`
|
|
8
|
+
* registrars. Each tool's Zod schema (declared once in `tools.ts`)
|
|
9
|
+
* drives both runtime input validation and the JSON Schema published
|
|
10
|
+
* to `tools/list` — eliminating the hand-written-schema-vs-handler
|
|
11
|
+
* drift that the low-level `setRequestHandler` pattern is prone to.
|
|
12
|
+
*
|
|
13
|
+
* Forwarded tools (`kind: 'forward'`) share a generic forwarder that
|
|
14
|
+
* looks up the binding, dispatches to LAP, and caches description
|
|
15
|
+
* payloads where applicable. The two meta tools
|
|
16
|
+
* (`llui_connect_session`, `llui_disconnect_session`) carry custom
|
|
17
|
+
* handlers that mutate the BindingMap directly.
|
|
18
|
+
*/
|
|
7
19
|
export function createBridgeServer(deps) {
|
|
8
20
|
const server = new McpServer({ name: 'llui-agent', version: deps.version }, { capabilities: { tools: {}, prompts: {} } });
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
server
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
});
|
|
21
|
+
for (const desc of TOOL_DESCRIPTORS) {
|
|
22
|
+
registerToolDescriptor(server, deps, desc);
|
|
23
|
+
}
|
|
24
|
+
registerPrompts(server);
|
|
25
|
+
return server;
|
|
26
|
+
}
|
|
27
|
+
function registerToolDescriptor(server, deps, desc) {
|
|
28
|
+
if (desc.kind === 'meta') {
|
|
29
|
+
if (desc.name === 'llui_connect_session') {
|
|
30
|
+
registerConnectSession(server, deps, desc);
|
|
33
31
|
}
|
|
34
|
-
if (name === 'llui_disconnect_session') {
|
|
32
|
+
else if (desc.name === 'llui_disconnect_session') {
|
|
33
|
+
registerDisconnectSession(server, deps, desc);
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
registerForwardedTool(server, deps, desc);
|
|
38
|
+
}
|
|
39
|
+
function registerConnectSession(server, deps, desc) {
|
|
40
|
+
server.registerTool(desc.name, { description: desc.description, inputSchema: desc.schema.shape }, async (args) => {
|
|
41
|
+
const { url, token } = args;
|
|
42
|
+
deps.bindings.set(deps.sessionId, url, token);
|
|
43
|
+
// Validate AND prefetch the bootstrap bundle in one call.
|
|
44
|
+
// /observe returns {state, actions, description, context} —
|
|
45
|
+
// exactly what the LLM needs to start acting. Without this,
|
|
46
|
+
// Claude has to follow up with `observe` to get anything
|
|
47
|
+
// usable, costing round-trips and creating a window where
|
|
48
|
+
// the connect tool's "you are now connected" result is the
|
|
49
|
+
// entire context the LLM has to reason about.
|
|
50
|
+
const res = await forwardLap(url, token, '/observe', {}, { fetch: deps.fetch });
|
|
51
|
+
if (!res.ok) {
|
|
35
52
|
deps.bindings.clear(deps.sessionId);
|
|
36
|
-
return
|
|
53
|
+
return errorResult(`connect failed: ${JSON.stringify(res.error)}`);
|
|
37
54
|
}
|
|
38
|
-
|
|
55
|
+
const observe = res.body;
|
|
56
|
+
deps.bindings.setDescribe(deps.sessionId, observe.description);
|
|
57
|
+
return okResult({
|
|
58
|
+
status: 'connected',
|
|
59
|
+
appName: observe.description.name,
|
|
60
|
+
appVersion: observe.description.version,
|
|
61
|
+
// Full observe payload — same shape the `observe` tool returns —
|
|
62
|
+
// so a `describe_app` / `get_state` / `list_actions` /
|
|
63
|
+
// `describe_context` follow-up is unnecessary on the first turn.
|
|
64
|
+
state: observe.state,
|
|
65
|
+
actions: observe.actions,
|
|
66
|
+
description: observe.description,
|
|
67
|
+
context: observe.context,
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function registerDisconnectSession(server, deps, desc) {
|
|
72
|
+
server.registerTool(desc.name, { description: desc.description, inputSchema: desc.schema.shape }, async () => {
|
|
73
|
+
deps.bindings.clear(deps.sessionId);
|
|
74
|
+
return okResult({ status: 'disconnected' });
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
function registerForwardedTool(server, deps, desc) {
|
|
78
|
+
server.registerTool(desc.name, { description: desc.description, inputSchema: desc.schema.shape }, async (args) => {
|
|
39
79
|
const binding = deps.bindings.get(deps.sessionId);
|
|
40
80
|
if (!binding) {
|
|
41
|
-
return errorResult('not bound — ask the user to
|
|
81
|
+
return errorResult('not bound — ask the user to copy the connect snippet from the LLui app, ' +
|
|
82
|
+
'or call `llui_connect_session` with the url and token they provide. ' +
|
|
83
|
+
'(In Claude Desktop only, the snippet is also available as the slash command `/llui-connect`.)');
|
|
42
84
|
}
|
|
43
|
-
// describe_app can serve from cache
|
|
44
|
-
if (name === 'describe_app' && binding.describe) {
|
|
85
|
+
// describe_app can serve from cache when one is available.
|
|
86
|
+
if (desc.name === 'describe_app' && binding.describe) {
|
|
45
87
|
return okResult(binding.describe);
|
|
46
88
|
}
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const res = await forwardLap(binding.url, binding.token, lapPath, args, { fetch: deps.fetch });
|
|
89
|
+
const res = await forwardLap(binding.url, binding.token, desc.lapPath, args ?? {}, {
|
|
90
|
+
fetch: deps.fetch,
|
|
91
|
+
});
|
|
51
92
|
if (!res.ok) {
|
|
52
|
-
return errorResult(`LAP ${lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`);
|
|
93
|
+
return errorResult(`LAP ${desc.lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`);
|
|
53
94
|
}
|
|
54
|
-
// Cache describe_app responses after the first call too
|
|
55
|
-
if (name === 'describe_app') {
|
|
95
|
+
// Cache describe_app responses after the first call too.
|
|
96
|
+
if (desc.name === 'describe_app') {
|
|
56
97
|
deps.bindings.setDescribe(deps.sessionId, res.body);
|
|
57
98
|
}
|
|
58
99
|
// observe returns description on every call; cache it so a later
|
|
59
|
-
// describe_app
|
|
60
|
-
|
|
61
|
-
if (name === 'observe') {
|
|
100
|
+
// describe_app can short-circuit the LAP round-trip.
|
|
101
|
+
if (desc.name === 'observe') {
|
|
62
102
|
const obs = res.body;
|
|
63
103
|
if (obs?.description)
|
|
64
104
|
deps.bindings.setDescribe(deps.sessionId, obs.description);
|
|
65
105
|
}
|
|
66
106
|
return okResult(res.body);
|
|
67
107
|
});
|
|
68
|
-
registerPrompts(server);
|
|
69
|
-
return server;
|
|
70
108
|
}
|
|
71
109
|
function okResult(body) {
|
|
110
|
+
// structuredContent is what current Claude clients (Desktop + CC)
|
|
111
|
+
// consume preferentially when present — typed JSON instead of a
|
|
112
|
+
// stringified blob. The `content` array stays as a `text` fallback
|
|
113
|
+
// so older clients still see something sensible.
|
|
72
114
|
return {
|
|
115
|
+
structuredContent: body,
|
|
73
116
|
content: [{ type: 'text', text: JSON.stringify(body) }],
|
|
74
117
|
};
|
|
75
118
|
}
|
package/dist/bridge.js.map
CHANGED
|
@@ -1 +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, LapObserveResponse } 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 // observe returns description on every call; cache it so a later\n // describe_app hit can serve from cache and short-circuit the LAP\n // round-trip.\n if (name === 'observe') {\n const obs = res.body as LapObserveResponse\n if (obs?.description) deps.bindings.setDescribe(deps.sessionId, obs.description)\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"]}
|
|
1
|
+
{"version":3,"file":"bridge.js","sourceRoot":"","sources":["../src/bridge.ts"],"names":[],"mappings":"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","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport type { CallToolResult } from '@modelcontextprotocol/sdk/types.js'\nimport { TOOL_DESCRIPTORS, type ToolDescriptor } from './tools.js'\nimport { BindingMap } from './binding.js'\nimport { forwardLap } from './forwarder.js'\nimport type { LapDescribeResponse, LapObserveResponse } 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\n/**\n * Builds the bridge's MCP server using the high-level `McpServer`\n * registrars. Each tool's Zod schema (declared once in `tools.ts`)\n * drives both runtime input validation and the JSON Schema published\n * to `tools/list` — eliminating the hand-written-schema-vs-handler\n * drift that the low-level `setRequestHandler` pattern is prone to.\n *\n * Forwarded tools (`kind: 'forward'`) share a generic forwarder that\n * looks up the binding, dispatches to LAP, and caches description\n * payloads where applicable. The two meta tools\n * (`llui_connect_session`, `llui_disconnect_session`) carry custom\n * handlers that mutate the BindingMap directly.\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 for (const desc of TOOL_DESCRIPTORS) {\n registerToolDescriptor(server, deps, desc)\n }\n\n registerPrompts(server)\n\n return server\n}\n\nfunction registerToolDescriptor(server: McpServer, deps: BridgeDeps, desc: ToolDescriptor): void {\n if (desc.kind === 'meta') {\n if (desc.name === 'llui_connect_session') {\n registerConnectSession(server, deps, desc)\n } else if (desc.name === 'llui_disconnect_session') {\n registerDisconnectSession(server, deps, desc)\n }\n return\n }\n registerForwardedTool(server, deps, desc)\n}\n\nfunction registerConnectSession(server: McpServer, deps: BridgeDeps, desc: ToolDescriptor): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async (args) => {\n const { url, token } = args as { url: string; token: string }\n deps.bindings.set(deps.sessionId, url, token)\n // Validate AND prefetch the bootstrap bundle in one call.\n // /observe returns {state, actions, description, context} —\n // exactly what the LLM needs to start acting. Without this,\n // Claude has to follow up with `observe` to get anything\n // usable, costing round-trips and creating a window where\n // the connect tool's \"you are now connected\" result is the\n // entire context the LLM has to reason about.\n const res = await forwardLap(url, token, '/observe', {}, { 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 observe = res.body as LapObserveResponse\n deps.bindings.setDescribe(deps.sessionId, observe.description)\n return okResult({\n status: 'connected',\n appName: observe.description.name,\n appVersion: observe.description.version,\n // Full observe payload — same shape the `observe` tool returns —\n // so a `describe_app` / `get_state` / `list_actions` /\n // `describe_context` follow-up is unnecessary on the first turn.\n state: observe.state,\n actions: observe.actions,\n description: observe.description,\n context: observe.context,\n })\n },\n )\n}\n\nfunction registerDisconnectSession(\n server: McpServer,\n deps: BridgeDeps,\n desc: ToolDescriptor,\n): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async () => {\n deps.bindings.clear(deps.sessionId)\n return okResult({ status: 'disconnected' })\n },\n )\n}\n\nfunction registerForwardedTool(\n server: McpServer,\n deps: BridgeDeps,\n desc: Extract<ToolDescriptor, { kind: 'forward' }>,\n): void {\n server.registerTool(\n desc.name,\n { description: desc.description, inputSchema: desc.schema.shape },\n async (args) => {\n const binding = deps.bindings.get(deps.sessionId)\n if (!binding) {\n return errorResult(\n 'not bound — ask the user to copy the connect snippet from the LLui app, ' +\n 'or call `llui_connect_session` with the url and token they provide. ' +\n '(In Claude Desktop only, the snippet is also available as the slash command `/llui-connect`.)',\n )\n }\n\n // describe_app can serve from cache when one is available.\n if (desc.name === 'describe_app' && binding.describe) {\n return okResult(binding.describe)\n }\n\n const res = await forwardLap(binding.url, binding.token, desc.lapPath, args ?? {}, {\n fetch: deps.fetch,\n })\n if (!res.ok) {\n return errorResult(\n `LAP ${desc.lapPath} failed: status=${res.status} ${JSON.stringify(res.error)}`,\n )\n }\n\n // Cache describe_app responses after the first call too.\n if (desc.name === 'describe_app') {\n deps.bindings.setDescribe(deps.sessionId, res.body as LapDescribeResponse)\n }\n\n // observe returns description on every call; cache it so a later\n // describe_app can short-circuit the LAP round-trip.\n if (desc.name === 'observe') {\n const obs = res.body as LapObserveResponse\n if (obs?.description) deps.bindings.setDescribe(deps.sessionId, obs.description)\n }\n\n return okResult(res.body)\n },\n )\n}\n\nfunction okResult(body: unknown): CallToolResult {\n // structuredContent is what current Claude clients (Desktop + CC)\n // consume preferentially when present — typed JSON instead of a\n // stringified blob. The `content` array stays as a `text` fallback\n // so older clients still see something sensible.\n return {\n structuredContent: body as Record<string, unknown>,\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/prompts.d.ts
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the bundled `llui-connect` MCP prompt. Both Claude Desktop
|
|
4
|
+
* and Claude Code surface it as a slash command (Desktop:
|
|
5
|
+
* `/llui-connect <url> <token>`; CC: `/mcp__<server>__llui-connect …`).
|
|
6
|
+
* The prompt body Claude sees is the same natural-language instruction
|
|
7
|
+
* the LLui app shows in its connect snippet — so pasting either form
|
|
8
|
+
* lands the same `llui_connect_session` tool call.
|
|
9
|
+
*/
|
|
2
10
|
export declare function registerPrompts(server: McpServer): void;
|
|
3
11
|
//# sourceMappingURL=prompts.d.ts.map
|
package/dist/prompts.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAA;AAGxE;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CA0BvD"}
|
package/dist/prompts.js
CHANGED
|
@@ -1,36 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Registers the bundled `llui-connect` MCP prompt. Both Claude Desktop
|
|
4
|
+
* and Claude Code surface it as a slash command (Desktop:
|
|
5
|
+
* `/llui-connect <url> <token>`; CC: `/mcp__<server>__llui-connect …`).
|
|
6
|
+
* The prompt body Claude sees is the same natural-language instruction
|
|
7
|
+
* the LLui app shows in its connect snippet — so pasting either form
|
|
8
|
+
* lands the same `llui_connect_session` tool call.
|
|
9
|
+
*/
|
|
2
10
|
export function registerPrompts(server) {
|
|
3
|
-
server.
|
|
4
|
-
|
|
11
|
+
server.registerPrompt('llui-connect', {
|
|
12
|
+
description: 'Bind this Claude conversation to an LLui app. Paste the URL and token the app showed you.',
|
|
13
|
+
argsSchema: {
|
|
14
|
+
url: z.string().describe('LAP base URL'),
|
|
15
|
+
token: z.string().describe('Bearer token'),
|
|
16
|
+
},
|
|
17
|
+
}, ({ url, token }) => ({
|
|
18
|
+
description: `Bind to LLui app at ${url}`,
|
|
19
|
+
messages: [
|
|
5
20
|
{
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
21
|
+
role: 'user',
|
|
22
|
+
content: {
|
|
23
|
+
type: 'text',
|
|
24
|
+
text: `Please connect this conversation to the LLui app at ${url}. ` +
|
|
25
|
+
`Call llui_connect_session with url=${JSON.stringify(url)} and token=${JSON.stringify(token)}.`,
|
|
26
|
+
},
|
|
12
27
|
},
|
|
13
28
|
],
|
|
14
29
|
}));
|
|
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
30
|
}
|
|
36
31
|
//# sourceMappingURL=prompts.js.map
|
package/dist/prompts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
1
|
+
{"version":3,"file":"prompts.js","sourceRoot":"","sources":["../src/prompts.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAEvB;;;;;;;GAOG;AACH,MAAM,UAAU,eAAe,CAAC,MAAiB;IAC/C,MAAM,CAAC,cAAc,CACnB,cAAc,EACd;QACE,WAAW,EACT,2FAA2F;QAC7F,UAAU,EAAE;YACV,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;YACxC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,cAAc,CAAC;SAC3C;KACF,EACD,CAAC,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACnB,WAAW,EAAE,uBAAuB,GAAG,EAAE;QACzC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE;oBACP,IAAI,EAAE,MAAM;oBACZ,IAAI,EACF,uDAAuD,GAAG,IAAI;wBAC9D,sCAAsC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,cAAc,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG;iBAClG;aACF;SACF;KACF,CAAC,CACH,CAAA;AACH,CAAC","sourcesContent":["import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'\nimport { z } from 'zod'\n\n/**\n * Registers the bundled `llui-connect` MCP prompt. Both Claude Desktop\n * and Claude Code surface it as a slash command (Desktop:\n * `/llui-connect <url> <token>`; CC: `/mcp__<server>__llui-connect …`).\n * The prompt body Claude sees is the same natural-language instruction\n * the LLui app shows in its connect snippet — so pasting either form\n * lands the same `llui_connect_session` tool call.\n */\nexport function registerPrompts(server: McpServer): void {\n server.registerPrompt(\n 'llui-connect',\n {\n description:\n 'Bind this Claude conversation to an LLui app. Paste the URL and token the app showed you.',\n argsSchema: {\n url: z.string().describe('LAP base URL'),\n token: z.string().describe('Bearer token'),\n },\n },\n ({ url, token }) => ({\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"]}
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,27 +1,21 @@
|
|
|
1
|
-
import
|
|
2
|
-
/**
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
export declare const TOOLS: ListToolsResult['tools'];
|
|
22
|
-
/**
|
|
23
|
-
* Mapping from tool name → LAP path for the forwarded subset.
|
|
24
|
-
* Meta-tools handled separately in bridge.ts.
|
|
25
|
-
*/
|
|
26
|
-
export declare const TOOL_TO_LAP_PATH: Record<string, string>;
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** Descriptor for a tool that forwards directly to the bound LAP server. */
|
|
3
|
+
export interface ForwardedToolDescriptor {
|
|
4
|
+
kind: 'forward';
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
/** Zod schema defining the tool's input shape. */
|
|
8
|
+
schema: z.ZodObject<z.ZodRawShape>;
|
|
9
|
+
/** LAP endpoint path (relative to the binding's base URL). */
|
|
10
|
+
lapPath: string;
|
|
11
|
+
}
|
|
12
|
+
/** Descriptor for a tool whose handler is implemented in the bridge itself. */
|
|
13
|
+
export interface MetaToolDescriptor {
|
|
14
|
+
kind: 'meta';
|
|
15
|
+
name: string;
|
|
16
|
+
description: string;
|
|
17
|
+
schema: z.ZodObject<z.ZodRawShape>;
|
|
18
|
+
}
|
|
19
|
+
export type ToolDescriptor = ForwardedToolDescriptor | MetaToolDescriptor;
|
|
20
|
+
export declare const TOOL_DESCRIPTORS: ToolDescriptor[];
|
|
27
21
|
//# sourceMappingURL=tools.d.ts.map
|
package/dist/tools.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AA6BvB,4EAA4E;AAC5E,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,SAAS,CAAA;IACf,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,kDAAkD;IAClD,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;IAClC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,+EAA+E;AAC/E,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,WAAW,EAAE,MAAM,CAAA;IACnB,MAAM,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAA;CACnC;AAED,MAAM,MAAM,cAAc,GAAG,uBAAuB,GAAG,kBAAkB,CAAA;AAEzE,eAAO,MAAM,gBAAgB,EAAE,cAAc,EAoL5C,CAAA"}
|
package/dist/tools.js
CHANGED
|
@@ -1,159 +1,187 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
1
2
|
/**
|
|
2
|
-
*
|
|
3
|
+
* Tool catalogue exposed to Claude through the MCP bridge. Two tiers:
|
|
3
4
|
*
|
|
4
|
-
* - Efficient path (recommended)
|
|
5
|
-
* `observe` returns state + actions + description + context in
|
|
6
|
-
* call
|
|
7
|
-
* trio. `send_message` defaults to `waitFor:
|
|
8
|
-
* until the message queue goes idle (http/delay/debounce
|
|
9
|
-
* trips complete)
|
|
10
|
-
* meta. Together these cut the "check state → act → check
|
|
11
|
-
* loop from 5 round-trips to 2.
|
|
5
|
+
* - **Efficient path (recommended)**: `observe` + `send_message`.
|
|
6
|
+
* `observe` returns state + actions + description + context in a
|
|
7
|
+
* single LAP call, replacing the old describe_app + get_state +
|
|
8
|
+
* list_actions trio. `send_message` defaults to `waitFor:'drained'`,
|
|
9
|
+
* blocking until the message queue goes idle (http/delay/debounce
|
|
10
|
+
* round-trips complete) and returning the new state + actions +
|
|
11
|
+
* drain meta. Together these cut the "check state → act → check
|
|
12
|
+
* state" loop from 5 round-trips to 2.
|
|
12
13
|
*
|
|
13
|
-
* - Legacy / specialized
|
|
14
|
-
* `wait_for_change`. Kept for back-compat and niche
|
|
15
|
-
* scoped state reads via JSON pointer, external state
|
|
16
|
-
* integrations should prefer `observe`.
|
|
14
|
+
* - **Legacy / specialized**: `describe_app`, `get_state`,
|
|
15
|
+
* `list_actions`, `wait_for_change`. Kept for back-compat and niche
|
|
16
|
+
* uses (e.g. scoped state reads via JSON pointer, external state
|
|
17
|
+
* pushes). New integrations should prefer `observe`.
|
|
17
18
|
*
|
|
18
19
|
* Spec §8.
|
|
20
|
+
*
|
|
21
|
+
* The catalogue is the single source of truth — Zod schemas drive both
|
|
22
|
+
* runtime input validation and the JSON Schema published in
|
|
23
|
+
* `tools/list`. Forwarded tools also carry their LAP endpoint path so
|
|
24
|
+
* `bridge.ts` can register one generic forwarder that loops over them.
|
|
19
25
|
*/
|
|
20
|
-
|
|
26
|
+
const empty = z.object({});
|
|
27
|
+
export const TOOL_DESCRIPTORS = [
|
|
21
28
|
{
|
|
29
|
+
kind: 'meta',
|
|
22
30
|
name: 'llui_connect_session',
|
|
23
|
-
description: 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type: 'string',
|
|
29
|
-
description: 'LAP base URL (e.g. https://app.example/agent/lap/v1)',
|
|
30
|
-
},
|
|
31
|
-
token: { type: 'string', description: 'Bearer token for LAP calls' },
|
|
32
|
-
},
|
|
33
|
-
required: ['url', 'token'],
|
|
34
|
-
},
|
|
31
|
+
description: 'Bind this Claude conversation to a specific LLui app. Call ONCE per chat when the user pastes a connect snippet from the LLui app — the snippet contains the url and token to forward here. The result includes the full observe bundle ({state, actions, description, context}) so you have everything you need to start acting — no separate describe_app / get_state / list_actions / describe_context follow-up is required on the first turn. Use observe later when you want a refreshed snapshot.',
|
|
32
|
+
schema: z.object({
|
|
33
|
+
url: z.string().describe('LAP base URL (e.g. https://app.example/agent/lap/v1)'),
|
|
34
|
+
token: z.string().describe('Bearer token for LAP calls'),
|
|
35
|
+
}),
|
|
35
36
|
},
|
|
36
37
|
{
|
|
38
|
+
kind: 'meta',
|
|
37
39
|
name: 'llui_disconnect_session',
|
|
38
40
|
description: 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',
|
|
39
|
-
|
|
41
|
+
schema: empty,
|
|
40
42
|
},
|
|
41
43
|
{
|
|
44
|
+
kind: 'forward',
|
|
42
45
|
name: 'observe',
|
|
43
46
|
description: 'Unified snapshot — returns {state, actions, description, context} in one call. Use this as the default "what can I see, what can I do" read; prefer it over describe_app + get_state + list_actions. Typical flow: observe → send_message → (repeat). The response includes the static app description (name, version, msgSchema, docs) on every call so first-time callers do not need a separate describe_app.',
|
|
44
|
-
|
|
47
|
+
schema: empty,
|
|
48
|
+
lapPath: '/observe',
|
|
45
49
|
},
|
|
46
50
|
{
|
|
51
|
+
kind: 'forward',
|
|
47
52
|
name: 'describe_app',
|
|
48
53
|
description: "Return the bound app's name, version, state/message schemas, annotations, and static docs. Legacy — prefer `observe`, which includes this as `description`.",
|
|
49
|
-
|
|
54
|
+
schema: empty,
|
|
55
|
+
lapPath: '/describe',
|
|
50
56
|
},
|
|
51
57
|
{
|
|
58
|
+
kind: 'forward',
|
|
52
59
|
name: 'get_state',
|
|
53
60
|
description: 'Return the current app state. Optional `path` (JSON-pointer) to narrow the slice. Legacy for full-state reads — prefer `observe`. Still useful for scoped reads via JSON pointer.',
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
61
|
+
schema: z.object({
|
|
62
|
+
path: z.string().optional().describe('Optional JSON-pointer, e.g. "/user/name"'),
|
|
63
|
+
}),
|
|
64
|
+
lapPath: '/state',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
kind: 'forward',
|
|
68
|
+
name: 'query_state',
|
|
69
|
+
description: 'Read a single slice of state via JSON-pointer path. Returns `{found: true, value}` on hit or `{found: false, detail}` on miss (missing key, walking through null, etc.). Cheaper than `observe` when checking one field. Path syntax: `""` (whole state), `"/auth/user"`, `"/items/0/id"`, `"/key~1with~1slash"` (escaped `/`), `"/key~0tilde"` (escaped `~`).',
|
|
70
|
+
schema: z.object({
|
|
71
|
+
path: z.string().describe('JSON-pointer (RFC 6901) — `/auth/user` or `""` for whole state'),
|
|
72
|
+
}),
|
|
73
|
+
lapPath: '/query-state',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
kind: 'forward',
|
|
77
|
+
name: 'describe_recent_actions',
|
|
78
|
+
description: 'Return the most recent log entries for this session (newest first). Each `dispatched` entry includes a `stateDiff` showing what changed. Useful for self-correction over multi-step flows — read your own past dispatches without re-querying full state. Filter by `kind` (e.g. `"dispatched"`) to skip read-only entries.',
|
|
79
|
+
schema: z.object({
|
|
80
|
+
n: z.number().int().positive().optional().describe('How many entries to return (default 10)'),
|
|
81
|
+
kind: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe('Filter to a specific kind (e.g. "dispatched", "read", "error")'),
|
|
85
|
+
}),
|
|
86
|
+
lapPath: '/recent-actions',
|
|
60
87
|
},
|
|
61
88
|
{
|
|
89
|
+
kind: 'forward',
|
|
90
|
+
name: 'would_dispatch',
|
|
91
|
+
description: 'Predict what dispatching `msg` would do without committing it. Runs the reducer in isolation against current state and returns `{stateDiff, effects}`. Effects are listed but NOT executed — the cloud is not hit, analytics do not fire. Use this to weigh a candidate action before sending: "if I dispatch X, will it change Y?" Pure-reducer assumption: if the reducer branches on Date.now() / localStorage / random, prediction drifts from real dispatch by exactly that impurity.',
|
|
92
|
+
schema: z.object({
|
|
93
|
+
msg: z
|
|
94
|
+
.object({ type: z.string() })
|
|
95
|
+
.passthrough()
|
|
96
|
+
.describe('The candidate message; must have a `type` string'),
|
|
97
|
+
}),
|
|
98
|
+
lapPath: '/would-dispatch',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
kind: 'forward',
|
|
62
102
|
name: 'list_actions',
|
|
63
103
|
description: 'Return the currently-affordable actions: visible UI bindings plus agent-affordable registry entries, filtered by annotation gates. Legacy — prefer `observe`, which includes this as `actions`.',
|
|
64
|
-
|
|
104
|
+
schema: empty,
|
|
105
|
+
lapPath: '/actions',
|
|
65
106
|
},
|
|
66
107
|
{
|
|
108
|
+
kind: 'forward',
|
|
67
109
|
name: 'send_message',
|
|
68
|
-
description: 'Dispatch a message to the app. Blocks by default until the message queue goes idle (drain semantics — captures http/delay/debounce round-trips that feed back as messages). Returns {status,
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
110
|
+
description: 'Dispatch a message to the app. Blocks by default until the message queue goes idle (drain semantics — captures http/delay/debounce round-trips that feed back as messages). Returns {status, stateDiff, actions, drain} on dispatched, {status: "pending-confirmation", confirmId} when the variant is @requiresConfirm, or {status: "rejected", reason} on validation failures. By default the response carries `stateDiff` (a JSON-Patch-shaped delta) and not the full post-state — apply the diff to the snapshot you got from `connect`/`observe`. Pass `includeState: true` if you want the full snapshot back (rare; expensive on bandwidth and context for large states). `drain.timedOut: true` means the 5s cap was hit while messages were still arriving — follow up with `observe` to resync. `actions` in the response reflects the new state, so you normally do not need a separate `observe` after a send.',
|
|
111
|
+
schema: z.object({
|
|
112
|
+
msg: z
|
|
113
|
+
.object({ type: z.string() })
|
|
114
|
+
.passthrough()
|
|
115
|
+
.describe('The message to dispatch; must have a `type` string'),
|
|
116
|
+
reason: z
|
|
117
|
+
.string()
|
|
118
|
+
.optional()
|
|
119
|
+
.describe('User-facing rationale (required for confirm-gated variants)'),
|
|
120
|
+
waitFor: z
|
|
121
|
+
.enum(['drained', 'idle', 'none'])
|
|
122
|
+
.optional()
|
|
123
|
+
.describe('"drained" (default) waits for the message queue to go idle; "idle" flushes the update cycle only (no async effects); "none" is fire-and-forget.'),
|
|
124
|
+
drainQuietMs: z
|
|
125
|
+
.number()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe('Quiescence window for waitFor:"drained". Drain completes when no commit fires for this many ms. Default 100.'),
|
|
128
|
+
timeoutMs: z
|
|
129
|
+
.number()
|
|
130
|
+
.optional()
|
|
131
|
+
.describe('Hard cap on total wait. Default 5000. For waitFor:"drained", this bounds how long the drain loop runs; for pending-confirmation, how long to wait for user approval.'),
|
|
132
|
+
includeState: z
|
|
133
|
+
.boolean()
|
|
134
|
+
.optional()
|
|
135
|
+
.describe('Include the full post-drain `stateAfter` snapshot in the response. Default false — `stateDiff` is what callers normally need, and resending the full state on every dispatch wastes bandwidth and context. Set true only when you need a fresh snapshot back (e.g., after a long-running effect that may have produced changes the diff misses).'),
|
|
136
|
+
}),
|
|
137
|
+
lapPath: '/message',
|
|
93
138
|
},
|
|
94
139
|
{
|
|
140
|
+
kind: 'forward',
|
|
95
141
|
name: 'get_confirm_result',
|
|
96
142
|
description: 'Poll a pending-confirmation by confirmId. Returns confirmed / rejected / still-pending.',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
},
|
|
103
|
-
required: ['confirmId'],
|
|
104
|
-
},
|
|
143
|
+
schema: z.object({
|
|
144
|
+
confirmId: z.string(),
|
|
145
|
+
timeoutMs: z.number().optional(),
|
|
146
|
+
}),
|
|
147
|
+
lapPath: '/confirm-result',
|
|
105
148
|
},
|
|
106
149
|
{
|
|
150
|
+
kind: 'forward',
|
|
107
151
|
name: 'wait_for_change',
|
|
108
152
|
description: 'Long-poll for a state change. Returns changed / timeout. Specialized — use for external state pushes (WebSocket messages, timers) that arrive while Claude is idle. For the normal send-then-read loop, `send_message` with `waitFor:"drained"` already waits for effect round-trips.',
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
},
|
|
118
|
-
},
|
|
153
|
+
schema: z.object({
|
|
154
|
+
path: z
|
|
155
|
+
.string()
|
|
156
|
+
.optional()
|
|
157
|
+
.describe('Optional JSON-pointer to narrow which state changes trigger resolution'),
|
|
158
|
+
timeoutMs: z.number().optional(),
|
|
159
|
+
}),
|
|
160
|
+
lapPath: '/wait',
|
|
119
161
|
},
|
|
120
162
|
{
|
|
163
|
+
kind: 'forward',
|
|
121
164
|
name: 'query_dom',
|
|
122
165
|
description: 'Read elements tagged with data-agent="<name>" in the rendered UI.',
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
},
|
|
129
|
-
required: ['name'],
|
|
130
|
-
},
|
|
166
|
+
schema: z.object({
|
|
167
|
+
name: z.string(),
|
|
168
|
+
multiple: z.boolean().optional(),
|
|
169
|
+
}),
|
|
170
|
+
lapPath: '/query-dom',
|
|
131
171
|
},
|
|
132
172
|
{
|
|
173
|
+
kind: 'forward',
|
|
133
174
|
name: 'describe_visible_content',
|
|
134
175
|
description: 'Return a structured outline of the currently-visible data-agent-tagged subtrees.',
|
|
135
|
-
|
|
176
|
+
schema: empty,
|
|
177
|
+
lapPath: '/describe-visible',
|
|
136
178
|
},
|
|
137
179
|
{
|
|
180
|
+
kind: 'forward',
|
|
138
181
|
name: 'describe_context',
|
|
139
182
|
description: 'Return the current per-state narrative docs (agentContext) — what the user is trying to do right now.',
|
|
140
|
-
|
|
183
|
+
schema: empty,
|
|
184
|
+
lapPath: '/context',
|
|
141
185
|
},
|
|
142
186
|
];
|
|
143
|
-
/**
|
|
144
|
-
* Mapping from tool name → LAP path for the forwarded subset.
|
|
145
|
-
* Meta-tools handled separately in bridge.ts.
|
|
146
|
-
*/
|
|
147
|
-
export const TOOL_TO_LAP_PATH = {
|
|
148
|
-
observe: '/observe',
|
|
149
|
-
describe_app: '/describe',
|
|
150
|
-
get_state: '/state',
|
|
151
|
-
list_actions: '/actions',
|
|
152
|
-
send_message: '/message',
|
|
153
|
-
get_confirm_result: '/confirm-result',
|
|
154
|
-
wait_for_change: '/wait',
|
|
155
|
-
query_dom: '/query-dom',
|
|
156
|
-
describe_visible_content: '/describe-visible',
|
|
157
|
-
describe_context: '/context',
|
|
158
|
-
};
|
|
159
187
|
//# sourceMappingURL=tools.js.map
|
package/dist/tools.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,MAAM,KAAK,GAA6B;IAC7C;QACE,IAAI,EAAE,sBAAsB;QAC5B,WAAW,EACT,6KAA6K;QAC/K,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,GAAG,EAAE;oBACH,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,sDAAsD;iBACpE;gBACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,4BAA4B,EAAE;aACrE;YACD,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC;SAC3B;KACF;IACD;QACE,IAAI,EAAE,yBAAyB;QAC/B,WAAW,EACT,oGAAoG;QACtG,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,SAAS;QACf,WAAW,EACT,kZAAkZ;QACpZ,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,6JAA6J;QAC/J,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EACT,mLAAmL;QACrL,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,0CAA0C,EAAE;aAClF;SACF;KACF;IACD;QACE,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,iMAAiM;QACnM,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,cAAc;QACpB,WAAW,EACT,6lBAA6lB;QAC/lB,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oDAAoD,EAAE;gBAC1F,MAAM,EAAE;oBACN,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,6DAA6D;iBAC3E;gBACD,OAAO,EAAE;oBACP,IAAI,EAAE,QAAQ;oBACd,IAAI,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC;oBACjC,WAAW,EACT,iJAAiJ;iBACpJ;gBACD,YAAY,EAAE;oBACZ,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,8GAA8G;iBACjH;gBACD,SAAS,EAAE;oBACT,IAAI,EAAE,QAAQ;oBACd,WAAW,EACT,sKAAsK;iBACzK;aACF;YACD,QAAQ,EAAE,CAAC,KAAK,CAAC;SAClB;KACF;IACD;QACE,IAAI,EAAE,oBAAoB;QAC1B,WAAW,EACT,yFAAyF;QAC3F,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBAC7B,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aAC9B;YACD,QAAQ,EAAE,CAAC,WAAW,CAAC;SACxB;KACF;IACD;QACE,IAAI,EAAE,iBAAiB;QACvB,WAAW,EACT,uRAAuR;QACzR,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE;oBACJ,IAAI,EAAE,QAAQ;oBACd,WAAW,EAAE,wEAAwE;iBACtF;gBACD,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;aAC9B;SACF;KACF;IACD;QACE,IAAI,EAAE,WAAW;QACjB,WAAW,EAAE,mEAAmE;QAChF,WAAW,EAAE;YACX,IAAI,EAAE,QAAQ;YACd,UAAU,EAAE;gBACV,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;gBACxB,QAAQ,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE;aAC9B;YACD,QAAQ,EAAE,CAAC,MAAM,CAAC;SACnB;KACF;IACD;QACE,IAAI,EAAE,0BAA0B;QAChC,WAAW,EAAE,kFAAkF;QAC/F,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;IACD;QACE,IAAI,EAAE,kBAAkB;QACxB,WAAW,EACT,uGAAuG;QACzG,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,EAAE;KAChD;CACF,CAAA;AAED;;;GAGG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,OAAO,EAAE,UAAU;IACnB,YAAY,EAAE,WAAW;IACzB,SAAS,EAAE,QAAQ;IACnB,YAAY,EAAE,UAAU;IACxB,YAAY,EAAE,UAAU;IACxB,kBAAkB,EAAE,iBAAiB;IACrC,eAAe,EAAE,OAAO;IACxB,SAAS,EAAE,YAAY;IACvB,wBAAwB,EAAE,mBAAmB;IAC7C,gBAAgB,EAAE,UAAU;CAC7B,CAAA","sourcesContent":["import type { ListToolsResult } from '@modelcontextprotocol/sdk/types.js'\n\n/**\n * The MCP tools Claude sees. Two tiers:\n *\n * - Efficient path (recommended): `observe` + `send_message`.\n * `observe` returns state + actions + description + context in one\n * call — replacing the old describe_app + get_state + list_actions\n * trio. `send_message` defaults to `waitFor: 'drained'`, blocking\n * until the message queue goes idle (http/delay/debounce round\n * trips complete), then returns the new state + actions + drain\n * meta. Together these cut the \"check state → act → check state\"\n * loop from 5 round-trips to 2.\n *\n * - Legacy / specialized: `describe_app`, `get_state`, `list_actions`,\n * `wait_for_change`. Kept for back-compat and niche uses (e.g.\n * scoped state reads via JSON pointer, external state pushes). New\n * integrations should prefer `observe`.\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: 'observe',\n description:\n 'Unified snapshot — returns {state, actions, description, context} in one call. Use this as the default \"what can I see, what can I do\" read; prefer it over describe_app + get_state + list_actions. Typical flow: observe → send_message → (repeat). The response includes the static app description (name, version, msgSchema, docs) on every call so first-time callers do not need a separate describe_app.',\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. Legacy — prefer `observe`, which includes this as `description`.\",\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. Legacy for full-state reads — prefer `observe`. Still useful for scoped reads via JSON pointer.',\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. Legacy — prefer `observe`, which includes this as `actions`.',\n inputSchema: { type: 'object', properties: {} },\n },\n {\n name: 'send_message',\n description:\n 'Dispatch a message to the app. Blocks by default until the message queue goes idle (drain semantics — captures http/delay/debounce round-trips that feed back as messages). Returns {status, stateAfter, actions, drain} on dispatched, {status: \"pending-confirmation\", confirmId} when the variant is @requiresConfirm, or {status: \"rejected\", reason} on validation failures. `drain.timedOut: true` means the 5s cap was hit while messages were still arriving — follow up with `observe` to resync. `actions` in the response reflects the new state, so you normally do not need a separate `observe` after a send.',\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: {\n type: 'string',\n enum: ['drained', 'idle', 'none'],\n description:\n '\"drained\" (default) waits for the message queue to go idle; \"idle\" flushes the update cycle only (no async effects); \"none\" is fire-and-forget.',\n },\n drainQuietMs: {\n type: 'number',\n description:\n 'Quiescence window for waitFor:\"drained\". Drain completes when no commit fires for this many ms. Default 100.',\n },\n timeoutMs: {\n type: 'number',\n description:\n 'Hard cap on total wait. Default 5000. For waitFor:\"drained\", this bounds how long the drain loop runs; for pending-confirmation, how long to wait for user approval.',\n },\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:\n 'Long-poll for a state change. Returns changed / timeout. Specialized — use for external state pushes (WebSocket messages, timers) that arrive while Claude is idle. For the normal send-then-read loop, `send_message` with `waitFor:\"drained\"` already waits for effect round-trips.',\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 observe: '/observe',\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"]}
|
|
1
|
+
{"version":3,"file":"tools.js","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"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","sourcesContent":["import { z } from 'zod'\n\n/**\n * Tool catalogue exposed to Claude through the MCP bridge. Two tiers:\n *\n * - **Efficient path (recommended)**: `observe` + `send_message`.\n * `observe` returns state + actions + description + context in a\n * single LAP call, replacing the old describe_app + get_state +\n * list_actions trio. `send_message` defaults to `waitFor:'drained'`,\n * blocking until the message queue goes idle (http/delay/debounce\n * round-trips complete) and returning the new state + actions +\n * drain meta. Together these cut the \"check state → act → check\n * state\" loop from 5 round-trips to 2.\n *\n * - **Legacy / specialized**: `describe_app`, `get_state`,\n * `list_actions`, `wait_for_change`. Kept for back-compat and niche\n * uses (e.g. scoped state reads via JSON pointer, external state\n * pushes). New integrations should prefer `observe`.\n *\n * Spec §8.\n *\n * The catalogue is the single source of truth — Zod schemas drive both\n * runtime input validation and the JSON Schema published in\n * `tools/list`. Forwarded tools also carry their LAP endpoint path so\n * `bridge.ts` can register one generic forwarder that loops over them.\n */\n\nconst empty = z.object({})\n\n/** Descriptor for a tool that forwards directly to the bound LAP server. */\nexport interface ForwardedToolDescriptor {\n kind: 'forward'\n name: string\n description: string\n /** Zod schema defining the tool's input shape. */\n schema: z.ZodObject<z.ZodRawShape>\n /** LAP endpoint path (relative to the binding's base URL). */\n lapPath: string\n}\n\n/** Descriptor for a tool whose handler is implemented in the bridge itself. */\nexport interface MetaToolDescriptor {\n kind: 'meta'\n name: string\n description: string\n schema: z.ZodObject<z.ZodRawShape>\n}\n\nexport type ToolDescriptor = ForwardedToolDescriptor | MetaToolDescriptor\n\nexport const TOOL_DESCRIPTORS: ToolDescriptor[] = [\n {\n kind: 'meta',\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 a connect snippet from the LLui app — the snippet contains the url and token to forward here. The result includes the full observe bundle ({state, actions, description, context}) so you have everything you need to start acting — no separate describe_app / get_state / list_actions / describe_context follow-up is required on the first turn. Use observe later when you want a refreshed snapshot.',\n schema: z.object({\n url: z.string().describe('LAP base URL (e.g. https://app.example/agent/lap/v1)'),\n token: z.string().describe('Bearer token for LAP calls'),\n }),\n },\n {\n kind: 'meta',\n name: 'llui_disconnect_session',\n description:\n 'Clear the binding for this Claude conversation. Subsequent LLui tool calls will fail until rebind.',\n schema: empty,\n },\n {\n kind: 'forward',\n name: 'observe',\n description:\n 'Unified snapshot — returns {state, actions, description, context} in one call. Use this as the default \"what can I see, what can I do\" read; prefer it over describe_app + get_state + list_actions. Typical flow: observe → send_message → (repeat). The response includes the static app description (name, version, msgSchema, docs) on every call so first-time callers do not need a separate describe_app.',\n schema: empty,\n lapPath: '/observe',\n },\n {\n kind: 'forward',\n name: 'describe_app',\n description:\n \"Return the bound app's name, version, state/message schemas, annotations, and static docs. Legacy — prefer `observe`, which includes this as `description`.\",\n schema: empty,\n lapPath: '/describe',\n },\n {\n kind: 'forward',\n name: 'get_state',\n description:\n 'Return the current app state. Optional `path` (JSON-pointer) to narrow the slice. Legacy for full-state reads — prefer `observe`. Still useful for scoped reads via JSON pointer.',\n schema: z.object({\n path: z.string().optional().describe('Optional JSON-pointer, e.g. \"/user/name\"'),\n }),\n lapPath: '/state',\n },\n {\n kind: 'forward',\n name: 'query_state',\n description:\n 'Read a single slice of state via JSON-pointer path. Returns `{found: true, value}` on hit or `{found: false, detail}` on miss (missing key, walking through null, etc.). Cheaper than `observe` when checking one field. Path syntax: `\"\"` (whole state), `\"/auth/user\"`, `\"/items/0/id\"`, `\"/key~1with~1slash\"` (escaped `/`), `\"/key~0tilde\"` (escaped `~`).',\n schema: z.object({\n path: z.string().describe('JSON-pointer (RFC 6901) — `/auth/user` or `\"\"` for whole state'),\n }),\n lapPath: '/query-state',\n },\n {\n kind: 'forward',\n name: 'describe_recent_actions',\n description:\n 'Return the most recent log entries for this session (newest first). Each `dispatched` entry includes a `stateDiff` showing what changed. Useful for self-correction over multi-step flows — read your own past dispatches without re-querying full state. Filter by `kind` (e.g. `\"dispatched\"`) to skip read-only entries.',\n schema: z.object({\n n: z.number().int().positive().optional().describe('How many entries to return (default 10)'),\n kind: z\n .string()\n .optional()\n .describe('Filter to a specific kind (e.g. \"dispatched\", \"read\", \"error\")'),\n }),\n lapPath: '/recent-actions',\n },\n {\n kind: 'forward',\n name: 'would_dispatch',\n description:\n 'Predict what dispatching `msg` would do without committing it. Runs the reducer in isolation against current state and returns `{stateDiff, effects}`. Effects are listed but NOT executed — the cloud is not hit, analytics do not fire. Use this to weigh a candidate action before sending: \"if I dispatch X, will it change Y?\" Pure-reducer assumption: if the reducer branches on Date.now() / localStorage / random, prediction drifts from real dispatch by exactly that impurity.',\n schema: z.object({\n msg: z\n .object({ type: z.string() })\n .passthrough()\n .describe('The candidate message; must have a `type` string'),\n }),\n lapPath: '/would-dispatch',\n },\n {\n kind: 'forward',\n name: 'list_actions',\n description:\n 'Return the currently-affordable actions: visible UI bindings plus agent-affordable registry entries, filtered by annotation gates. Legacy — prefer `observe`, which includes this as `actions`.',\n schema: empty,\n lapPath: '/actions',\n },\n {\n kind: 'forward',\n name: 'send_message',\n description:\n 'Dispatch a message to the app. Blocks by default until the message queue goes idle (drain semantics — captures http/delay/debounce round-trips that feed back as messages). Returns {status, stateDiff, actions, drain} on dispatched, {status: \"pending-confirmation\", confirmId} when the variant is @requiresConfirm, or {status: \"rejected\", reason} on validation failures. By default the response carries `stateDiff` (a JSON-Patch-shaped delta) and not the full post-state — apply the diff to the snapshot you got from `connect`/`observe`. Pass `includeState: true` if you want the full snapshot back (rare; expensive on bandwidth and context for large states). `drain.timedOut: true` means the 5s cap was hit while messages were still arriving — follow up with `observe` to resync. `actions` in the response reflects the new state, so you normally do not need a separate `observe` after a send.',\n schema: z.object({\n msg: z\n .object({ type: z.string() })\n .passthrough()\n .describe('The message to dispatch; must have a `type` string'),\n reason: z\n .string()\n .optional()\n .describe('User-facing rationale (required for confirm-gated variants)'),\n waitFor: z\n .enum(['drained', 'idle', 'none'])\n .optional()\n .describe(\n '\"drained\" (default) waits for the message queue to go idle; \"idle\" flushes the update cycle only (no async effects); \"none\" is fire-and-forget.',\n ),\n drainQuietMs: z\n .number()\n .optional()\n .describe(\n 'Quiescence window for waitFor:\"drained\". Drain completes when no commit fires for this many ms. Default 100.',\n ),\n timeoutMs: z\n .number()\n .optional()\n .describe(\n 'Hard cap on total wait. Default 5000. For waitFor:\"drained\", this bounds how long the drain loop runs; for pending-confirmation, how long to wait for user approval.',\n ),\n includeState: z\n .boolean()\n .optional()\n .describe(\n 'Include the full post-drain `stateAfter` snapshot in the response. Default false — `stateDiff` is what callers normally need, and resending the full state on every dispatch wastes bandwidth and context. Set true only when you need a fresh snapshot back (e.g., after a long-running effect that may have produced changes the diff misses).',\n ),\n }),\n lapPath: '/message',\n },\n {\n kind: 'forward',\n name: 'get_confirm_result',\n description:\n 'Poll a pending-confirmation by confirmId. Returns confirmed / rejected / still-pending.',\n schema: z.object({\n confirmId: z.string(),\n timeoutMs: z.number().optional(),\n }),\n lapPath: '/confirm-result',\n },\n {\n kind: 'forward',\n name: 'wait_for_change',\n description:\n 'Long-poll for a state change. Returns changed / timeout. Specialized — use for external state pushes (WebSocket messages, timers) that arrive while Claude is idle. For the normal send-then-read loop, `send_message` with `waitFor:\"drained\"` already waits for effect round-trips.',\n schema: z.object({\n path: z\n .string()\n .optional()\n .describe('Optional JSON-pointer to narrow which state changes trigger resolution'),\n timeoutMs: z.number().optional(),\n }),\n lapPath: '/wait',\n },\n {\n kind: 'forward',\n name: 'query_dom',\n description: 'Read elements tagged with data-agent=\"<name>\" in the rendered UI.',\n schema: z.object({\n name: z.string(),\n multiple: z.boolean().optional(),\n }),\n lapPath: '/query-dom',\n },\n {\n kind: 'forward',\n name: 'describe_visible_content',\n description: 'Return a structured outline of the currently-visible data-agent-tagged subtrees.',\n schema: empty,\n lapPath: '/describe-visible',\n },\n {\n kind: 'forward',\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 schema: empty,\n lapPath: '/context',\n },\n]\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "llui-agent",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"llui-agent": "./dist/cli.js"
|
|
@@ -20,7 +20,8 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
23
|
-
"
|
|
23
|
+
"zod": "^4.0.0",
|
|
24
|
+
"@llui/agent": "0.0.34"
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
27
|
"@types/node": "^22.0.0"
|