@slexkit/mcp 0.3.2 → 0.4.0

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.
@@ -6,290 +6,296 @@ ToolHost is the structured-input path for confirmations, choices, and forms. Do
6
6
 
7
7
  Raw Markdown: /docs/reference/toolhost.md
8
8
 
9
- ---
10
- title: ToolHost
11
- category: Reference
12
- status: ready
13
- order: 70
14
- summary: "Structured user-input UI for confirmations, choices, forms, templates, and submit boundaries."
15
- slexkitRenderMode: component
16
- ---
17
-
18
- # ToolHost
19
-
20
- ToolHost bridges AI tool calls to interactive UI. Instead of the AI executing tools server-side, ToolHost renders a structured input form in the browser and returns the user's response as a `ToolResult`.
21
-
22
- ## Concepts
23
-
24
- When an AI model emits a tool call (e.g., `confirm-action`, `fill-form`), ToolHost compiles it into a standard `SlexExpression` and mounts it using the core runtime. The mounted UI has a **submit boundary** (`submit:actions` component) that settles a Promise with the user's input.
25
-
26
- ToolHost is **separate from display-oriented `slex` fences**. Display components render information; ToolHost components collect structured user input and return it programmatically.
27
-
28
- ## Public API
29
-
30
- ### `renderToolCall(call, container) → ToolRenderHandle`
31
-
32
- Compiles a tool call into a SlexExpression, mounts it, and returns a handle.
33
-
34
- ```ts
35
- function renderToolCall(call: ToolCall, container: HTMLElement): ToolRenderHandle;
36
- ```
37
-
38
- ```ts
39
- type ToolRenderHandle = {
40
- promise: Promise<ToolResult>; // Resolves when user submits or ignores
41
- dispose: () => void; // Clean up the rendered UI
42
- };
43
-
44
- type ToolResult =
45
- | { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
46
- | { toolCallId?: string; toolName: string; status: "ignored"; value: null };
47
- ```
48
-
49
- **Usage:**
50
-
51
- ```js
52
- import { renderToolCall } from "slexkit";
53
-
54
- const handle = renderToolCall(
55
- {
56
- id: "call_001",
57
- name: "confirm-action",
58
- arguments: {
59
- title: "Delete file?",
60
- description: "This action cannot be undone.",
61
- confirmLabel: "Delete",
62
- requireReason: true,
63
- },
64
- },
65
- document.getElementById("tool-container"),
66
- );
67
-
68
- handle.promise.then((result) => {
69
- console.log(result.status); // "submitted" or "ignored"
70
- console.log(result.value); // { confirmed: true, reason: "Obsolete file" }
71
- handle.dispose();
72
- });
73
- ```
74
-
75
- ### `registerToolTemplate(name, compiler)`
76
-
77
- Register a custom tool template. The compiler function receives the tool arguments and returns a `SlexExpression`.
78
-
79
- ```ts
80
- function registerToolTemplate(name: string, compiler: ToolTemplateCompiler): void;
81
-
82
- type ToolTemplateCompiler<TArgs = Record<string, unknown>> = (
83
- args: TArgs,
84
- runtime: ToolRuntime,
85
- call: ToolCall,
86
- ) => SlexExpression;
87
-
88
- type ToolRuntime = {
89
- submit: (value: Record<string, unknown>) => void;
90
- ignore: () => void;
91
- };
92
- ```
93
-
94
- ## Built-in templates
95
-
96
- ### `confirm-action`
97
-
98
- Yes/no confirmation dialog. Supports an optional reason field.
99
-
100
- **Arguments (`ConfirmActionArguments`):**
101
-
102
- | Field | Type | Default | Description |
103
- |-------|------|---------|-------------|
104
- | `title` | `string` | `"Confirm action"` | Dialog title |
105
- | `description` | `string` | — | Optional explanation text |
106
- | `confirmLabel` | `string` | `"Confirm"` | Submit button label |
107
- | `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
108
- | `requireReason` | `boolean` | `false` | Show a reason text input |
109
- | `reasonLabel` | `string` | `"Reason"` | Label for the reason input |
110
- | `reasonPlaceholder` | `string` | `"Add a reason"` | Placeholder for the reason input |
111
-
112
- **Result value:**
113
-
114
- | Key | Type | Description |
115
- |-----|------|-------------|
116
- | `confirmed` | `boolean` | Always `true` when submitted |
117
- | `reason` | `string` | User's reason text (only if `requireReason: true`) |
118
-
119
- **Example:**
120
-
121
- ```js
122
- renderToolCall({
123
- name: "confirm-action",
124
- arguments: {
125
- title: "Delete project?",
126
- description: "This will permanently delete all files.",
127
- confirmLabel: "Delete",
128
- requireReason: true,
129
- },
130
- }, container);
131
- ```
132
-
133
- ### `choose-options` / `option-list`
134
-
135
- Single or multi-select option list. `option-list` is an alias for `choose-options` — behavior is identical.
136
-
137
- **Arguments (`ChooseOptionsArguments` / `OptionListArguments`):**
138
-
139
- | Field | Type | Default | Description |
140
- |-------|------|---------|-------------|
141
- | `title` | `string` | `"Choose options"` | Dialog title |
142
- | `description` | `string` | — | Optional explanation text |
143
- | `options` / `items` | `OptionListItem[]` | `[]` | List of selectable items |
144
- | `multiple` | `boolean` | `true` | Allow multiple selection |
145
- | `selected` | `string[]` | auto | Pre-selected item IDs |
146
- | `minSelected` | `number` | `0` | Minimum required selections |
147
- | `maxSelected` | `number` | `Infinity` | Maximum allowed selections |
148
- | `submitLabel` | `string` | `"Submit"` | Submit button label |
149
- | `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
150
-
151
- **OptionListItem:**
152
-
153
- | Field | Type | Description |
154
- |-------|------|-------------|
155
- | `label` | `string` | Display text (required) |
156
- | `id` | `string` | Unique identifier (defaults to `value` or index) |
157
- | `value` | `string` | Submitted value (defaults to `id`) |
158
- | `description` | `string` | Secondary text shown after label |
159
- | `selected` | `boolean` | Pre-selected state |
160
- | `disabled` | `boolean` | Disabled state |
161
-
162
- **Rendering:**
163
- - When `multiple: true` (default): renders **checkboxes**
164
- - When `multiple: false`: renders a **radio group**
165
-
166
- **Result value:**
167
-
168
- | Key | Type | Description |
169
- |-----|------|-------------|
170
- | `selected` | `string[]` | IDs of selected items |
171
-
172
- **Example:**
173
-
174
- ```js
175
- renderToolCall({
176
- name: "choose-options",
177
- arguments: {
178
- title: "Select dependencies",
179
- options: [
180
- { id: "react", label: "React", description: "UI library" },
181
- { id: "vue", label: "Vue", description: "Progressive framework" },
182
- { id: "svelte", label: "Svelte", description: "Compiled framework", selected: true },
183
- ],
184
- multiple: true,
185
- minSelected: 1,
186
- maxSelected: 2,
187
- },
188
- }, container);
189
- ```
190
-
191
- ### `fill-form`
192
-
193
- Multi-field form with various field types.
194
-
195
- **Arguments (`FillFormArguments`):**
196
-
197
- | Field | Type | Default | Description |
198
- |-------|------|---------|-------------|
199
- | `title` | `string` | `"Fill form"` | Form title |
200
- | `description` | `string` | — | Optional explanation text |
201
- | `fields` | `FormField[]` | `[]` | Form field definitions |
202
- | `values` | `Record<string, unknown>` | — | Initial field values |
203
- | `submitLabel` | `string` | `"Submit"` | Submit button label |
204
- | `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
205
-
206
- **FormField:**
207
-
208
- | Field | Type | Default | Description |
209
- |-------|------|---------|-------------|
210
- | `name` | `string` | (required) | Field name (used as key in result value) |
211
- | `type` | `FormFieldType` | `"text"` | Field type |
212
- | `label` | `string` | `name` | Display label |
213
- | `description` | `string` | — | Help text below the field |
214
- | `placeholder` | `string` | — | Input placeholder |
215
- | `required` | `boolean` | `false` | Require non-empty value to submit |
216
- | `disabled` | `boolean` | `false` | Disable the field |
217
- | `value` | `unknown` | — | Initial value |
218
- | `options` | `Array<{label, value}>` | — | Options for `select` type |
219
-
220
- **FormFieldType:**
221
-
222
- | Type | Renders | Description |
223
- |------|---------|-------------|
224
- | `text` | `<input>` | Free-form text |
225
- | `number` | `<input type="number">` | Numeric input, value is `number \| null` |
226
- | `engineering` | `<input>` with SI prefix parsing | Accepts values like `"10kΩ"`, `"100nF"`, `"1.5MegHz"`. Parsed with `parseEngineeringNumber()`. State includes `raw`, `number`, `valid`, `prefix`, `unit`, `normalized` |
227
- | `select` | `<select>` | Dropdown from `options` array |
228
- | `checkbox` | Checkbox | Boolean toggle |
229
- | `switch` | Switch toggle | Boolean toggle (switch style) |
230
-
231
- **Result value:**
232
-
233
- An object with a key for each field name, containing the submitted value. For `engineering` fields, the raw input string is submitted. For `checkbox`/`switch` fields, a boolean is submitted. For `number` fields, a number or `null` is submitted.
234
-
235
- **Example:**
236
-
237
- ```js
238
- renderToolCall({
239
- name: "fill-form",
240
- arguments: {
241
- title: "New project",
242
- fields: [
243
- { name: "name", type: "text", label: "Project name", required: true },
244
- { name: "framework", type: "select", label: "Framework", options: [
245
- { label: "React", value: "react" },
246
- { label: "Svelte", value: "svelte" },
247
- ]},
248
- { name: "resistance", type: "engineering", label: "Target resistance", placeholder: "e.g. 10kΩ" },
249
- { name: "typescript", type: "checkbox", label: "Use TypeScript", value: true },
250
- ],
251
- },
252
- }, container);
253
- ```
254
-
255
- ## Writing a custom template
256
-
257
- Implement the `ToolTemplateCompiler` function and register it:
258
-
259
- ```js
260
- import { registerToolTemplate, renderToolCall } from "slexkit";
261
-
262
- registerToolTemplate("greet-user", (args, runtime, call) => ({
263
- namespace: `tool_greet_${Date.now()}`,
264
- g: {
265
- __slexkitTool: runtime,
266
- name: args.name || "World",
267
- },
268
- layout: {
269
- "card:tool": {
270
- title: "Greeting",
271
- "text:message": { "$content": "'Hello, ' + g.name + '!'" },
272
- "submit:actions": {
273
- returnKeys: ["name"],
274
- submitLabel: args.submitLabel || "OK",
275
- ignoreLabel: args.ignoreLabel || "Cancel",
276
- },
277
- },
278
- },
279
- }));
280
-
281
- renderToolCall(
282
- { name: "greet-user", arguments: { name: "Alice" } },
283
- container,
284
- );
285
- ```
286
-
287
- ### Key patterns
288
-
289
- 1. **`__slexkitTool`** — Set `g.__slexkitTool = runtime` to give expressions access to `submit()` and `ignore()`
290
- 2. **`submit:actions`** — Always include a `submit:actions` component as the completion boundary. Set `returnKeys` to the property names that should be included in the result value
291
- 3. **`canSubmit`** — Add a `g.canSubmit()` method to control the submit button's disabled state
292
- 4. **Unique namespace** — Use a timestamp-based namespace to avoid collisions with other tool calls
9
+ ---
10
+ title: ToolHost
11
+ category: Reference
12
+ status: ready
13
+ order: 70
14
+ summary: "Structured user-input UI for confirmations, choices, forms, and templates."
15
+ slexkitRenderMode: component
16
+ ---
17
+
18
+ # ToolHost
19
+
20
+ ToolHost bridges tool calls to interactive UI. When a model asks for confirmation, a choice, or form input, ToolHost renders the structured UI in the browser and returns the user's response as a `ToolResult`.
21
+
22
+ ## Concepts
23
+
24
+ When a model emits a tool call such as `confirm-action` or `fill-form`, ToolHost compiles it into a standard `SlexExpression` and mounts it using the core runtime. The mounted UI uses `submit:actions` to submit the user's input and settle a Promise.
25
+
26
+ ToolHost is **separate from display-oriented `slex` fences**. Display components render information; ToolHost components collect structured user input and return it programmatically.
27
+
28
+ ### Submit
29
+
30
+ `submit:actions` reads the fields listed in `returnKeys`, calls the ToolHost runtime, and resolves the `ToolRenderHandle.promise` with a `ToolResult`.
31
+
32
+ Do not use `submit` in display fences or component demos. Use `button` for ordinary interactions; use `submit:actions` only when the host is waiting for a structured tool result.
33
+
34
+ ## Public API
35
+
36
+ ### `renderToolCall(call, container) → ToolRenderHandle`
37
+
38
+ Compiles a tool call into a SlexExpression, mounts it, and returns a handle.
39
+
40
+ ```ts
41
+ function renderToolCall(call: ToolCall, container: HTMLElement): ToolRenderHandle;
42
+ ```
43
+
44
+ ```ts
45
+ type ToolRenderHandle = {
46
+ promise: Promise<ToolResult>; // Resolves when user submits or ignores
47
+ dispose: () => void; // Clean up the rendered UI
48
+ };
49
+
50
+ type ToolResult =
51
+ | { toolCallId?: string; toolName: string; status: "submitted"; value: Record<string, unknown> }
52
+ | { toolCallId?: string; toolName: string; status: "ignored"; value: null };
53
+ ```
54
+
55
+ **Usage:**
56
+
57
+ ```js
58
+ import { renderToolCall } from "slexkit";
59
+
60
+ const handle = renderToolCall(
61
+ {
62
+ id: "call_001",
63
+ name: "confirm-action",
64
+ arguments: {
65
+ title: "Delete file?",
66
+ description: "This action cannot be undone.",
67
+ confirmLabel: "Delete",
68
+ requireReason: true,
69
+ },
70
+ },
71
+ document.getElementById("tool-container"),
72
+ );
73
+
74
+ handle.promise.then((result) => {
75
+ console.log(result.status); // "submitted" or "ignored"
76
+ console.log(result.value); // { confirmed: true, reason: "Obsolete file" }
77
+ handle.dispose();
78
+ });
79
+ ```
80
+
81
+ ### `registerToolTemplate(name, compiler)`
82
+
83
+ Register a custom tool template. The compiler function receives the tool arguments and returns a `SlexExpression`.
84
+
85
+ ```ts
86
+ function registerToolTemplate(name: string, compiler: ToolTemplateCompiler): void;
87
+
88
+ type ToolTemplateCompiler<TArgs = Record<string, unknown>> = (
89
+ args: TArgs,
90
+ runtime: ToolRuntime,
91
+ call: ToolCall,
92
+ ) => SlexExpression;
93
+
94
+ type ToolRuntime = {
95
+ submit: (value: Record<string, unknown>) => void;
96
+ ignore: () => void;
97
+ };
98
+ ```
99
+
100
+ ## Built-in templates
101
+
102
+ ### `confirm-action`
103
+
104
+ Yes/no confirmation dialog. Supports an optional reason field.
105
+
106
+ **Arguments (`ConfirmActionArguments`):**
107
+
108
+ | Field | Type | Default | Description |
109
+ |-------|------|---------|-------------|
110
+ | `title` | `string` | `"Confirm action"` | Dialog title |
111
+ | `description` | `string` | — | Optional explanation text |
112
+ | `confirmLabel` | `string` | `"Confirm"` | Submit button label |
113
+ | `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
114
+ | `requireReason` | `boolean` | `false` | Show a reason text input |
115
+ | `reasonLabel` | `string` | `"Reason"` | Label for the reason input |
116
+ | `reasonPlaceholder` | `string` | `"Add a reason"` | Placeholder for the reason input |
117
+
118
+ **Result value:**
119
+
120
+ | Key | Type | Description |
121
+ |-----|------|-------------|
122
+ | `confirmed` | `boolean` | Always `true` when submitted |
123
+ | `reason` | `string` | User's reason text (only if `requireReason: true`) |
124
+
125
+ **Example:**
126
+
127
+ ```js
128
+ renderToolCall({
129
+ name: "confirm-action",
130
+ arguments: {
131
+ title: "Delete project?",
132
+ description: "This will permanently delete all files.",
133
+ confirmLabel: "Delete",
134
+ requireReason: true,
135
+ },
136
+ }, container);
137
+ ```
138
+
139
+ ### `choose-options` / `option-list`
140
+
141
+ Single or multi-select option list. `option-list` is an alias for `choose-options` — behavior is identical.
142
+
143
+ **Arguments (`ChooseOptionsArguments` / `OptionListArguments`):**
144
+
145
+ | Field | Type | Default | Description |
146
+ |-------|------|---------|-------------|
147
+ | `title` | `string` | `"Choose options"` | Dialog title |
148
+ | `description` | `string` | — | Optional explanation text |
149
+ | `options` / `items` | `OptionListItem[]` | `[]` | List of selectable items |
150
+ | `multiple` | `boolean` | `true` | Allow multiple selection |
151
+ | `selected` | `string[]` | auto | Pre-selected item IDs |
152
+ | `minSelected` | `number` | `0` | Minimum required selections |
153
+ | `maxSelected` | `number` | `Infinity` | Maximum allowed selections |
154
+ | `submitLabel` | `string` | `"Submit"` | Submit button label |
155
+ | `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
156
+
157
+ **OptionListItem:**
158
+
159
+ | Field | Type | Description |
160
+ |-------|------|-------------|
161
+ | `label` | `string` | Display text (required) |
162
+ | `id` | `string` | Unique identifier (defaults to `value` or index) |
163
+ | `value` | `string` | Submitted value (defaults to `id`) |
164
+ | `description` | `string` | Secondary text shown after label |
165
+ | `selected` | `boolean` | Pre-selected state |
166
+ | `disabled` | `boolean` | Disabled state |
167
+
168
+ **Rendering:**
169
+ - When `multiple: true` (default): renders **checkboxes**
170
+ - When `multiple: false`: renders a **radio group**
171
+
172
+ **Result value:**
173
+
174
+ | Key | Type | Description |
175
+ |-----|------|-------------|
176
+ | `selected` | `string[]` | IDs of selected items |
177
+
178
+ **Example:**
179
+
180
+ ```js
181
+ renderToolCall({
182
+ name: "choose-options",
183
+ arguments: {
184
+ title: "Select dependencies",
185
+ options: [
186
+ { id: "react", label: "React", description: "UI library" },
187
+ { id: "vue", label: "Vue", description: "Progressive framework" },
188
+ { id: "svelte", label: "Svelte", description: "Compiled framework", selected: true },
189
+ ],
190
+ multiple: true,
191
+ minSelected: 1,
192
+ maxSelected: 2,
193
+ },
194
+ }, container);
195
+ ```
196
+
197
+ ### `fill-form`
198
+
199
+ Multi-field form with various field types.
200
+
201
+ **Arguments (`FillFormArguments`):**
202
+
203
+ | Field | Type | Default | Description |
204
+ |-------|------|---------|-------------|
205
+ | `title` | `string` | `"Fill form"` | Form title |
206
+ | `description` | `string` | — | Optional explanation text |
207
+ | `fields` | `FormField[]` | `[]` | Form field definitions |
208
+ | `values` | `Record<string, unknown>` | — | Initial field values |
209
+ | `submitLabel` | `string` | `"Submit"` | Submit button label |
210
+ | `ignoreLabel` | `string` | `"Ignore"` | Cancel button label |
211
+
212
+ **FormField:**
213
+
214
+ | Field | Type | Default | Description |
215
+ |-------|------|---------|-------------|
216
+ | `name` | `string` | (required) | Field name (used as key in result value) |
217
+ | `type` | `FormFieldType` | `"text"` | Field type |
218
+ | `label` | `string` | `name` | Display label |
219
+ | `description` | `string` | — | Help text below the field |
220
+ | `placeholder` | `string` | — | Input placeholder |
221
+ | `required` | `boolean` | `false` | Require non-empty value to submit |
222
+ | `disabled` | `boolean` | `false` | Disable the field |
223
+ | `value` | `unknown` | — | Initial value |
224
+ | `options` | `Array<{label, value}>` | — | Options for `select` type |
225
+
226
+ **FormFieldType:**
227
+
228
+ | Type | Renders | Description |
229
+ |------|---------|-------------|
230
+ | `text` | `<input>` | Free-form text |
231
+ | `number` | `<input type="number">` | Numeric input, value is `number \| null` |
232
+ | `engineering` | `<input>` with SI prefix parsing | Accepts values like `"10kΩ"`, `"100nF"`, `"1.5MegHz"`. Parsed with `parseEngineeringNumber()`. State includes `raw`, `number`, `valid`, `prefix`, `unit`, `normalized` |
233
+ | `select` | `<select>` | Dropdown from `options` array |
234
+ | `checkbox` | Checkbox | Boolean toggle |
235
+ | `switch` | Switch toggle | Boolean toggle (switch style) |
236
+
237
+ **Result value:**
238
+
239
+ An object with a key for each field name, containing the submitted value. For `engineering` fields, the raw input string is submitted. For `checkbox`/`switch` fields, a boolean is submitted. For `number` fields, a number or `null` is submitted.
240
+
241
+ **Example:**
242
+
243
+ ```js
244
+ renderToolCall({
245
+ name: "fill-form",
246
+ arguments: {
247
+ title: "New project",
248
+ fields: [
249
+ { name: "name", type: "text", label: "Project name", required: true },
250
+ { name: "framework", type: "select", label: "Framework", options: [
251
+ { label: "React", value: "react" },
252
+ { label: "Svelte", value: "svelte" },
253
+ ]},
254
+ { name: "resistance", type: "engineering", label: "Target resistance", placeholder: "e.g. 10kΩ" },
255
+ { name: "typescript", type: "checkbox", label: "Use TypeScript", value: true },
256
+ ],
257
+ },
258
+ }, container);
259
+ ```
260
+
261
+ ## Writing a custom template
262
+
263
+ Implement the `ToolTemplateCompiler` function and register it:
264
+
265
+ ```js
266
+ import { registerToolTemplate, renderToolCall } from "slexkit";
267
+
268
+ registerToolTemplate("greet-user", (args, runtime, call) => ({
269
+ namespace: `tool_greet_${Date.now()}`,
270
+ g: {
271
+ __slexkitTool: runtime,
272
+ name: args.name || "World",
273
+ },
274
+ layout: {
275
+ "card:tool": {
276
+ title: "Greeting",
277
+ "text:message": { "$content": "'Hello, ' + g.name + '!'" },
278
+ "submit:actions": {
279
+ returnKeys: ["name"],
280
+ submitLabel: args.submitLabel || "OK",
281
+ ignoreLabel: args.ignoreLabel || "Cancel",
282
+ },
283
+ },
284
+ },
285
+ }));
286
+
287
+ renderToolCall(
288
+ { name: "greet-user", arguments: { name: "Alice" } },
289
+ container,
290
+ );
291
+ ```
292
+
293
+ ### Key patterns
294
+
295
+ 1. **`__slexkitTool`** — Set `g.__slexkitTool = runtime` to give expressions access to `submit()` and `ignore()`
296
+ 2. **`submit:actions`** — Always include a `submit:actions` component to submit the result. Set `returnKeys` to the property names that should be included in the result value
297
+ 3. **`canSubmit`** — Add a `g.canSubmit()` method to control the submit button's disabled state
298
+ 4. **Unique namespace** — Use a timestamp-based namespace to avoid collisions with other tool calls
293
299
  5. **Validation** — Implement validation logic in `g` methods and wire `$disabled` on the submit button
294
300
 
295
301
  ---