@leanmcp/elicitation 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +139 -352
  2. package/package.json +9 -3
package/README.md CHANGED
@@ -1,16 +1,41 @@
1
- # @leanmcp/elicitation
2
-
3
- Structured user input collection for LeanMCP tools using the MCP elicitation protocol. The `@Elicitation` decorator automatically intercepts tool calls to request missing required fields from users before execution.
1
+ <p align="center">
2
+ <img
3
+ src="https://raw.githubusercontent.com/LeanMCP/leanmcp-sdk/refs/heads/main/assets/logo.png"
4
+ alt="LeanMCP Logo"
5
+ width="400"
6
+ />
7
+ </p>
8
+
9
+ <p align="center">
10
+ <strong>@leanmcp/elicitation</strong><br/>
11
+ Structured user input collection for MCP tools using the elicitation protocol.
12
+ </p>
13
+
14
+ <p align="center">
15
+ <a href="https://www.npmjs.com/package/@leanmcp/elicitation">
16
+ <img src="https://img.shields.io/npm/v/@leanmcp/elicitation" alt="npm version" />
17
+ </a>
18
+ <a href="https://www.npmjs.com/package/@leanmcp/elicitation">
19
+ <img src="https://img.shields.io/npm/dm/@leanmcp/elicitation" alt="npm downloads" />
20
+ </a>
21
+ <a href="https://docs.leanmcp.com/sdk/elicitation">
22
+ <img src="https://img.shields.io/badge/Docs-leanmcp-0A66C2?" />
23
+ </a>
24
+ <a href="https://discord.com/invite/DsRcA3GwPy">
25
+ <img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" />
26
+ </a>
27
+ <a href="https://x.com/LeanMcp">
28
+ <img src="https://img.shields.io/badge/@LeanMCP-f5f5f5?logo=x&logoColor=000000" />
29
+ </a>
30
+ </p>
4
31
 
5
32
  ## Features
6
33
 
7
- - **@Elicitation decorator** - Declarative way to collect missing user inputs
8
- - **Method wrapping** - Automatically intercepts calls and returns elicitation requests
9
- - **Multiple strategies** - Form, multi-step, and conversational elicitation
10
- - **Fluent builder API** - Programmatic form creation with type safety
11
- - **Built-in validation** - Email, URL, pattern matching, custom validators
12
- - **Conditional elicitation** - Only ask for inputs when needed
13
- - **Type-safe** - Full TypeScript support with type inference
34
+ - **@Elicitation Decorator** Automatically collect missing user inputs before tool execution
35
+ - **Fluent Builder API** Programmatic form creation with `ElicitationFormBuilder`
36
+ - **Multiple Strategies** Form and multi-step elicitation
37
+ - **Built-in Validation** min/max, pattern matching, custom validators
38
+ - **Conditional Elicitation** Only ask for inputs when needed
14
39
 
15
40
  ## Installation
16
41
 
@@ -20,7 +45,7 @@ npm install @leanmcp/elicitation @leanmcp/core
20
45
 
21
46
  ## Quick Start
22
47
 
23
- ### 1. Simple Form Elicitation
48
+ ### Simple Form Elicitation
24
49
 
25
50
  ```typescript
26
51
  import { Tool } from "@leanmcp/core";
@@ -51,15 +76,81 @@ class SlackService {
51
76
  ]
52
77
  })
53
78
  async createChannel(args: { channelName: string; isPrivate: boolean }) {
54
- // Implementation
55
79
  return { success: true, channelName: args.channelName };
56
80
  }
57
81
  }
58
82
  ```
59
83
 
60
- ### 2. Conditional Elicitation
84
+ ### How It Works
85
+
86
+ 1. **Client calls tool** with missing required fields
87
+ 2. **Decorator intercepts** and checks for missing fields
88
+ 3. **Elicitation request returned** with form definition
89
+ 4. **Client displays form** to collect user input
90
+ 5. **Client calls tool again** with complete arguments
91
+ 6. **Method executes** normally
92
+
93
+ ---
94
+
95
+ ## Fluent Builder API
96
+
97
+ For more complex forms, use `ElicitationFormBuilder`:
98
+
99
+ ```typescript
100
+ import { Tool } from "@leanmcp/core";
101
+ import { Elicitation, ElicitationFormBuilder, validation } from "@leanmcp/elicitation";
102
+
103
+ class UserService {
104
+ @Tool({ description: "Create user account" })
105
+ @Elicitation({
106
+ builder: () => new ElicitationFormBuilder()
107
+ .title("User Registration")
108
+ .description("Create a new user account")
109
+ .addEmailField("email", "Email Address", { required: true })
110
+ .addTextField("username", "Username", {
111
+ required: true,
112
+ validation: validation()
113
+ .minLength(3)
114
+ .maxLength(20)
115
+ .pattern("^[a-zA-Z0-9_]+$")
116
+ .build()
117
+ })
118
+ .addSelectField("role", "Role", [
119
+ { label: "Admin", value: "admin" },
120
+ { label: "User", value: "user" }
121
+ ])
122
+ .build()
123
+ })
124
+ async createUser(args: any) {
125
+ return { success: true, email: args.email };
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### Builder Methods
61
131
 
62
- Only ask for inputs when they're missing:
132
+ | Method | Description |
133
+ |--------|-------------|
134
+ | `title(string)` | Set form title |
135
+ | `description(string)` | Set form description |
136
+ | `condition(fn)` | Set condition for elicitation |
137
+ | `addTextField(name, label, opts?)` | Add text input |
138
+ | `addTextAreaField(name, label, opts?)` | Add textarea |
139
+ | `addNumberField(name, label, opts?)` | Add number input |
140
+ | `addBooleanField(name, label, opts?)` | Add checkbox |
141
+ | `addSelectField(name, label, options, opts?)` | Add dropdown |
142
+ | `addMultiSelectField(name, label, options, opts?)` | Add multi-select |
143
+ | `addEmailField(name, label, opts?)` | Add email input |
144
+ | `addUrlField(name, label, opts?)` | Add URL input |
145
+ | `addDateField(name, label, opts?)` | Add date picker |
146
+ | `addCustomField(field)` | Add custom field |
147
+ | `build()` | Build final config |
148
+
149
+ ---
150
+
151
+ ## Conditional Elicitation
152
+
153
+ Only ask for inputs when needed:
63
154
 
64
155
  ```typescript
65
156
  @Tool({ description: "Send message to Slack" })
@@ -84,41 +175,11 @@ async sendMessage(args: { channelId?: string; message: string }) {
84
175
  }
85
176
  ```
86
177
 
87
- ### 3. Fluent Builder API
178
+ ---
88
179
 
89
- More programmatic approach:
180
+ ## Multi-Step Elicitation
90
181
 
91
- ```typescript
92
- import { ElicitationFormBuilder, validation } from "@leanmcp/elicitation";
93
-
94
- @Tool({ description: "Create user account" })
95
- @Elicitation({
96
- builder: () => new ElicitationFormBuilder()
97
- .title("User Registration")
98
- .description("Create a new user account")
99
- .addEmailField("email", "Email Address", { required: true })
100
- .addTextField("username", "Username", {
101
- required: true,
102
- validation: validation()
103
- .minLength(3)
104
- .maxLength(20)
105
- .pattern("^[a-zA-Z0-9_]+$")
106
- .build()
107
- })
108
- .addSelectField("role", "Role", [
109
- { label: "Admin", value: "admin" },
110
- { label: "User", value: "user" }
111
- ])
112
- .build()
113
- })
114
- async createUser(args: any) {
115
- // Implementation
116
- }
117
- ```
118
-
119
- ### 4. Multi-Step Elicitation
120
-
121
- Break input collection into multiple steps:
182
+ Break input collection into sequential steps:
122
183
 
123
184
  ```typescript
124
185
  @Tool({ description: "Deploy application" })
@@ -159,123 +220,27 @@ async deployApp(args: any) {
159
220
  }
160
221
  ```
161
222
 
162
- ## Field Types
163
-
164
- ### Text Fields
165
-
166
- ```typescript
167
- {
168
- name: "description",
169
- label: "Description",
170
- type: "text",
171
- placeholder: "Enter description...",
172
- validation: {
173
- minLength: 10,
174
- maxLength: 500
175
- }
176
- }
177
- ```
178
-
179
- ### Textarea
180
-
181
- ```typescript
182
- {
183
- name: "content",
184
- label: "Content",
185
- type: "textarea",
186
- placeholder: "Enter long text..."
187
- }
188
- ```
189
-
190
- ### Number
191
-
192
- ```typescript
193
- {
194
- name: "age",
195
- label: "Age",
196
- type: "number",
197
- validation: {
198
- min: 18,
199
- max: 120
200
- }
201
- }
202
- ```
203
-
204
- ### Boolean (Checkbox)
205
-
206
- ```typescript
207
- {
208
- name: "agree",
209
- label: "I agree to terms",
210
- type: "boolean",
211
- defaultValue: false
212
- }
213
- ```
214
-
215
- ### Select (Dropdown)
216
-
217
- ```typescript
218
- {
219
- name: "country",
220
- label: "Country",
221
- type: "select",
222
- options: [
223
- { label: "United States", value: "US" },
224
- { label: "Canada", value: "CA" }
225
- ]
226
- }
227
- ```
228
-
229
- ### Multi-Select
223
+ ---
230
224
 
231
- ```typescript
232
- {
233
- name: "tags",
234
- label: "Tags",
235
- type: "multiselect",
236
- options: [
237
- { label: "JavaScript", value: "js" },
238
- { label: "TypeScript", value: "ts" },
239
- { label: "Python", value: "py" }
240
- ]
241
- }
242
- ```
243
-
244
- ### Email
245
-
246
- ```typescript
247
- {
248
- name: "email",
249
- label: "Email",
250
- type: "email",
251
- required: true
252
- }
253
- ```
254
-
255
- ### URL
256
-
257
- ```typescript
258
- {
259
- name: "website",
260
- label: "Website",
261
- type: "url",
262
- placeholder: "https://example.com"
263
- }
264
- ```
225
+ ## Field Types
265
226
 
266
- ### Date
227
+ | Type | Description |
228
+ |------|-------------|
229
+ | `text` | Single-line text input |
230
+ | `textarea` | Multi-line text area |
231
+ | `number` | Numeric input |
232
+ | `boolean` | Checkbox |
233
+ | `select` | Dropdown (single choice) |
234
+ | `multiselect` | Multi-select |
235
+ | `email` | Email input |
236
+ | `url` | URL input |
237
+ | `date` | Date picker |
267
238
 
268
- ```typescript
269
- {
270
- name: "birthdate",
271
- label: "Birth Date",
272
- type: "date"
273
- }
274
- ```
239
+ ---
275
240
 
276
241
  ## Validation
277
242
 
278
- ### Built-in Validators
243
+ ### Built-in Validation
279
244
 
280
245
  ```typescript
281
246
  {
@@ -291,29 +256,6 @@ async deployApp(args: any) {
291
256
  }
292
257
  ```
293
258
 
294
- ### Custom Validators
295
-
296
- ```typescript
297
- {
298
- name: "password",
299
- label: "Password",
300
- type: "text",
301
- validation: {
302
- customValidator: (value) => {
303
- const hasUpper = /[A-Z]/.test(value);
304
- const hasLower = /[a-z]/.test(value);
305
- const hasNumber = /[0-9]/.test(value);
306
-
307
- if (!hasUpper || !hasLower || !hasNumber) {
308
- return "Password must contain uppercase, lowercase, and numbers";
309
- }
310
-
311
- return true; // Valid
312
- }
313
- }
314
- }
315
- ```
316
-
317
259
  ### Using ValidationBuilder
318
260
 
319
261
  ```typescript
@@ -328,103 +270,7 @@ validation()
328
270
  .build()
329
271
  ```
330
272
 
331
- ## How It Works
332
-
333
- 1. **Client calls tool** with missing required fields
334
- 2. **Decorator intercepts** the method call before execution
335
- 3. **Elicitation check** determines if required fields are missing
336
- 4. **Elicitation request returned** if fields are missing
337
- 5. **Client displays form** to collect user input
338
- 6. **Client calls tool again** with complete arguments
339
- 7. **Method executes** normally with all required fields
340
-
341
- **Key Benefits:**
342
- - **Automatic interception** - No need to modify `@leanmcp/core`
343
- - **Clean separation** - Elicitation logic separate from business logic
344
- - **MCP compliant** - Follows MCP elicitation protocol
345
- - **Type-safe** - Full TypeScript support
346
-
347
- ## Strategies
348
-
349
- ### Form Strategy (Default)
350
-
351
- Collect all fields at once:
352
-
353
- ```typescript
354
- @Elicitation({
355
- strategy: "form", // or omit, form is default
356
- title: "User Information",
357
- fields: [/* ... */]
358
- })
359
- ```
360
-
361
- ### Multi-Step Strategy
362
-
363
- Break input collection into sequential steps:
364
-
365
- ```typescript
366
- @Elicitation({
367
- strategy: "multi-step",
368
- builder: () => [
369
- {
370
- title: "Step 1: Basic Info",
371
- fields: [/* step 1 fields */]
372
- },
373
- {
374
- title: "Step 2: Details",
375
- fields: [/* step 2 fields */],
376
- condition: (prev) => prev.needsDetails === true
377
- }
378
- ]
379
- })
380
- ```
381
-
382
- ## Elicitation Flow
383
-
384
- ### Request/Response Cycle
385
-
386
- **First Call (Missing Fields):**
387
- ```json
388
- // Request
389
- {
390
- "method": "tools/call",
391
- "params": {
392
- "name": "createChannel",
393
- "arguments": {}
394
- }
395
- }
396
-
397
- // Response (Elicitation Request)
398
- {
399
- "content": [{
400
- "type": "text",
401
- "text": "{\n \"type\": \"elicitation\",\n \"title\": \"Create Channel\",\n \"fields\": [...]\n}"
402
- }]
403
- }
404
- ```
405
-
406
- **Second Call (Complete Fields):**
407
- ```json
408
- // Request
409
- {
410
- "method": "tools/call",
411
- "params": {
412
- "name": "createChannel",
413
- "arguments": {
414
- "channelName": "my-channel",
415
- "isPrivate": false
416
- }
417
- }
418
- }
419
-
420
- // Response (Tool Result)
421
- {
422
- "content": [{
423
- "type": "text",
424
- "text": "{\"success\": true, \"channelId\": \"C123\"}"
425
- }]
426
- }
427
- ```
273
+ ---
428
274
 
429
275
  ## API Reference
430
276
 
@@ -472,92 +318,33 @@ interface FieldValidation {
472
318
  }
473
319
  ```
474
320
 
475
- ## Complete Example
476
-
477
- See [examples/slack-with-elicitation](../../examples/slack-with-elicitation) for a complete working example.
478
-
479
- ```typescript
480
- import { createHTTPServer, MCPServer, Tool } from "@leanmcp/core";
481
- import { Elicitation, ElicitationFormBuilder, validation } from "@leanmcp/elicitation";
321
+ ---
482
322
 
483
- class SlackService {
484
- @Tool({ description: "Create a new Slack channel" })
485
- @Elicitation({
486
- title: "Create Channel",
487
- description: "Please provide channel details",
488
- fields: [
489
- {
490
- name: "channelName",
491
- label: "Channel Name",
492
- type: "text",
493
- required: true,
494
- validation: {
495
- pattern: "^[a-z0-9-]+$",
496
- errorMessage: "Must be lowercase alphanumeric with hyphens"
497
- }
498
- },
499
- {
500
- name: "isPrivate",
501
- label: "Private Channel",
502
- type: "boolean",
503
- defaultValue: false
504
- }
505
- ]
506
- })
507
- async createChannel(args: { channelName: string; isPrivate: boolean }) {
508
- return {
509
- success: true,
510
- channelId: `C${Date.now()}`,
511
- channelName: args.channelName
512
- };
513
- }
514
- }
515
-
516
- // Start server
517
- const serverFactory = () => {
518
- const server = new MCPServer({ name: "slack-server", version: "1.0.0" });
519
- server.registerService(new SlackService());
520
- return server.getServer();
521
- };
522
-
523
- await createHTTPServer(serverFactory, { port: 3000 });
524
- ```
525
-
526
- ## Error Handling
323
+ ## Best Practices
527
324
 
528
- ```typescript
529
- try {
530
- const result = await service.createChannel({ channelName: "test" });
531
- console.log(result);
532
- } catch (error) {
533
- console.error('Tool execution failed:', error);
534
- }
535
- ```
325
+ 1. **Use conditional elicitation** — Only ask when truly needed
326
+ 2. **Provide sensible defaults** — Reduce user input burden
327
+ 3. **Clear field labels** Make fields self-explanatory
328
+ 4. **Validate early** — Catch errors before submission
329
+ 5. **Use builder for complex forms** — Fluent API is more maintainable
536
330
 
537
- If elicitation is needed, the method returns an `ElicitationRequest` object instead of throwing an error.
331
+ ---
538
332
 
539
- ## Best Practices
333
+ ## Documentation
540
334
 
541
- 1. **Use conditional elicitation** - Only ask when truly needed
542
- 2. **Provide sensible defaults** - Reduce user input burden
543
- 3. **Clear field labels** - Make fields self-explanatory
544
- 4. **Validate early** - Catch errors before submission
545
- 5. **Group related fields** - Use multi-step for complex forms
546
- 6. **Test thoroughly** - Test both elicitation and execution paths
547
- 7. **Use builder for complex forms** - Fluent API is more maintainable
548
- 8. **Add help text** - Guide users with helpful descriptions
335
+ - [Full Documentation](https://docs.leanmcp.com/sdk/elicitation)
549
336
 
550
337
  ## Related Packages
551
338
 
552
- - [@leanmcp/core](../core) - Core MCP server functionality
553
- - [@leanmcp/auth](../auth) - Authentication for MCP tools
554
- - [@leanmcp/utils](../utils) - Utility functions
339
+ - [@leanmcp/core](https://www.npmjs.com/package/@leanmcp/core) Core MCP server functionality
340
+ - [@leanmcp/auth](https://www.npmjs.com/package/@leanmcp/auth) Authentication decorators
341
+ - [@leanmcp/cli](https://www.npmjs.com/package/@leanmcp/cli) CLI for project scaffolding
555
342
 
556
343
  ## Links
557
344
 
558
345
  - [GitHub Repository](https://github.com/LeanMCP/leanmcp-sdk)
559
- - [Documentation](https://github.com/LeanMCP/leanmcp-sdk#readme)
560
- - [Example Implementation](../../examples/slack-with-elicitation)
346
+ - [NPM Package](https://www.npmjs.com/package/@leanmcp/elicitation)
347
+ - [MCP Specification](https://spec.modelcontextprotocol.io/)
561
348
 
562
349
  ## License
563
350
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leanmcp/elicitation",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Elicitation support for LeanMCP - structured user input collection",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -21,12 +21,18 @@
21
21
  "build": "tsup src/index.ts --format cjs,esm --dts",
22
22
  "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23
23
  "clean": "rm -rf dist",
24
- "test": "jest",
24
+ "test": "jest --passWithNoTests",
25
25
  "lint": "eslint src"
26
26
  },
27
27
  "dependencies": {
28
28
  "reflect-metadata": "^0.2.0"
29
29
  },
30
+ "devDependencies": {
31
+ "@types/jest": "^29.5.0",
32
+ "@types/node": "^20.0.0",
33
+ "jest": "^29.7.0",
34
+ "ts-jest": "^29.1.0"
35
+ },
30
36
  "peerDependencies": {
31
37
  "@leanmcp/core": "*"
32
38
  },
@@ -55,4 +61,4 @@
55
61
  "publishConfig": {
56
62
  "access": "public"
57
63
  }
58
- }
64
+ }