@leanmcp/elicitation 0.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,665 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
3
+
4
+ // src/index.ts
5
+ import "reflect-metadata";
6
+
7
+ // src/types.ts
8
+ import "reflect-metadata";
9
+ var ElicitationError = class extends Error {
10
+ static {
11
+ __name(this, "ElicitationError");
12
+ }
13
+ code;
14
+ details;
15
+ constructor(message, code, details) {
16
+ super(message), this.code = code, this.details = details;
17
+ this.name = "ElicitationError";
18
+ }
19
+ };
20
+
21
+ // src/decorators.ts
22
+ import "reflect-metadata";
23
+
24
+ // src/strategies/base.ts
25
+ var ElicitationStrategyBase = class {
26
+ static {
27
+ __name(this, "ElicitationStrategyBase");
28
+ }
29
+ /**
30
+ * Validate user response against field definitions
31
+ */
32
+ validateResponse(response, fields) {
33
+ const errors = [];
34
+ for (const field of fields) {
35
+ const value = response.values[field.name];
36
+ if (field.required && (value === void 0 || value === null || value === "")) {
37
+ errors.push({
38
+ field: field.name,
39
+ message: field.validation?.errorMessage || `${field.label} is required`
40
+ });
41
+ continue;
42
+ }
43
+ if (value === void 0 || value === null || value === "") {
44
+ continue;
45
+ }
46
+ const typeError = this.validateFieldType(value, field);
47
+ if (typeError) {
48
+ errors.push(typeError);
49
+ continue;
50
+ }
51
+ if (field.validation) {
52
+ const validationError = this.validateField(value, field);
53
+ if (validationError) {
54
+ errors.push(validationError);
55
+ }
56
+ }
57
+ }
58
+ return errors;
59
+ }
60
+ /**
61
+ * Validate field type
62
+ */
63
+ validateFieldType(value, field) {
64
+ switch (field.type) {
65
+ case "number":
66
+ if (typeof value !== "number" && isNaN(Number(value))) {
67
+ return {
68
+ field: field.name,
69
+ message: `${field.label} must be a number`
70
+ };
71
+ }
72
+ break;
73
+ case "boolean":
74
+ if (typeof value !== "boolean") {
75
+ return {
76
+ field: field.name,
77
+ message: `${field.label} must be true or false`
78
+ };
79
+ }
80
+ break;
81
+ case "email":
82
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
83
+ if (!emailRegex.test(String(value))) {
84
+ return {
85
+ field: field.name,
86
+ message: `${field.label} must be a valid email address`
87
+ };
88
+ }
89
+ break;
90
+ case "url":
91
+ try {
92
+ new URL(String(value));
93
+ } catch {
94
+ return {
95
+ field: field.name,
96
+ message: `${field.label} must be a valid URL`
97
+ };
98
+ }
99
+ break;
100
+ }
101
+ return null;
102
+ }
103
+ /**
104
+ * Validate field against validation rules
105
+ */
106
+ validateField(value, field) {
107
+ const validation2 = field.validation;
108
+ if (!validation2) return null;
109
+ if (field.type === "number") {
110
+ const numValue = Number(value);
111
+ if (validation2.min !== void 0 && numValue < validation2.min) {
112
+ return {
113
+ field: field.name,
114
+ message: validation2.errorMessage || `${field.label} must be at least ${validation2.min}`
115
+ };
116
+ }
117
+ if (validation2.max !== void 0 && numValue > validation2.max) {
118
+ return {
119
+ field: field.name,
120
+ message: validation2.errorMessage || `${field.label} must be at most ${validation2.max}`
121
+ };
122
+ }
123
+ }
124
+ if (field.type === "text" || field.type === "textarea" || field.type === "email" || field.type === "url") {
125
+ const strValue = String(value);
126
+ if (validation2.minLength !== void 0 && strValue.length < validation2.minLength) {
127
+ return {
128
+ field: field.name,
129
+ message: validation2.errorMessage || `${field.label} must be at least ${validation2.minLength} characters`
130
+ };
131
+ }
132
+ if (validation2.maxLength !== void 0 && strValue.length > validation2.maxLength) {
133
+ return {
134
+ field: field.name,
135
+ message: validation2.errorMessage || `${field.label} must be at most ${validation2.maxLength} characters`
136
+ };
137
+ }
138
+ }
139
+ if (validation2.pattern) {
140
+ const regex = new RegExp(validation2.pattern);
141
+ if (!regex.test(String(value))) {
142
+ return {
143
+ field: field.name,
144
+ message: validation2.errorMessage || `${field.label} format is invalid`
145
+ };
146
+ }
147
+ }
148
+ if (validation2.customValidator) {
149
+ const result = validation2.customValidator(value);
150
+ if (result !== true) {
151
+ return {
152
+ field: field.name,
153
+ message: typeof result === "string" ? result : validation2.errorMessage || `${field.label} is invalid`
154
+ };
155
+ }
156
+ }
157
+ return null;
158
+ }
159
+ /**
160
+ * Merge elicited values with original arguments
161
+ */
162
+ mergeWithArgs(originalArgs, elicitedValues) {
163
+ return {
164
+ ...originalArgs,
165
+ ...elicitedValues
166
+ };
167
+ }
168
+ };
169
+
170
+ // src/strategies/form.ts
171
+ var FormElicitationStrategy = class extends ElicitationStrategyBase {
172
+ static {
173
+ __name(this, "FormElicitationStrategy");
174
+ }
175
+ buildRequest(config, context) {
176
+ return {
177
+ type: "elicitation",
178
+ title: config.title || "Additional Information Required",
179
+ description: config.description,
180
+ fields: config.fields || [],
181
+ metadata: {
182
+ strategy: "form",
183
+ previousValues: context.args
184
+ }
185
+ };
186
+ }
187
+ };
188
+
189
+ // src/strategies/multi-step.ts
190
+ var MultiStepElicitationStrategy = class extends ElicitationStrategyBase {
191
+ static {
192
+ __name(this, "MultiStepElicitationStrategy");
193
+ }
194
+ steps;
195
+ currentStep = 0;
196
+ accumulatedValues = {};
197
+ constructor(steps) {
198
+ super();
199
+ this.steps = steps;
200
+ }
201
+ buildRequest(config, context) {
202
+ while (this.currentStep < this.steps.length) {
203
+ const step = this.steps[this.currentStep];
204
+ if (!step.condition || step.condition(this.accumulatedValues)) {
205
+ return {
206
+ type: "elicitation",
207
+ title: step.title,
208
+ description: step.description,
209
+ fields: step.fields,
210
+ metadata: {
211
+ strategy: "multi-step",
212
+ stepNumber: this.currentStep + 1,
213
+ totalSteps: this.steps.length,
214
+ previousValues: {
215
+ ...context.args,
216
+ ...this.accumulatedValues
217
+ }
218
+ }
219
+ };
220
+ }
221
+ this.currentStep++;
222
+ }
223
+ return {
224
+ type: "elicitation",
225
+ title: "Complete",
226
+ description: "All steps completed",
227
+ fields: [],
228
+ metadata: {
229
+ strategy: "multi-step",
230
+ stepNumber: this.steps.length,
231
+ totalSteps: this.steps.length,
232
+ previousValues: this.accumulatedValues
233
+ }
234
+ };
235
+ }
236
+ /**
237
+ * Check if there are more steps
238
+ */
239
+ hasNextStep() {
240
+ return this.currentStep < this.steps.length - 1;
241
+ }
242
+ /**
243
+ * Move to next step and accumulate values
244
+ */
245
+ nextStep(values) {
246
+ this.accumulatedValues = {
247
+ ...this.accumulatedValues,
248
+ ...values
249
+ };
250
+ this.currentStep++;
251
+ }
252
+ /**
253
+ * Get all accumulated values
254
+ */
255
+ getAccumulatedValues() {
256
+ return {
257
+ ...this.accumulatedValues
258
+ };
259
+ }
260
+ /**
261
+ * Reset to first step
262
+ */
263
+ reset() {
264
+ this.currentStep = 0;
265
+ this.accumulatedValues = {};
266
+ }
267
+ /**
268
+ * Override merge to include all accumulated values
269
+ */
270
+ mergeWithArgs(originalArgs, elicitedValues) {
271
+ return {
272
+ ...originalArgs,
273
+ ...this.accumulatedValues,
274
+ ...elicitedValues
275
+ };
276
+ }
277
+ };
278
+
279
+ // src/decorators.ts
280
+ function checkMissingFields(args, config) {
281
+ if (!config.fields) return false;
282
+ for (const field of config.fields) {
283
+ if (field.required) {
284
+ const value = args[field.name];
285
+ if (value === void 0 || value === null || value === "") {
286
+ return true;
287
+ }
288
+ }
289
+ }
290
+ return false;
291
+ }
292
+ __name(checkMissingFields, "checkMissingFields");
293
+ function Elicitation(config) {
294
+ return (target, propertyKey, descriptor) => {
295
+ if (!descriptor || typeof descriptor.value !== "function") {
296
+ throw new Error("@Elicitation can only be applied to methods");
297
+ }
298
+ const originalMethod = descriptor.value;
299
+ Reflect.defineMetadata("elicitation:config", config, originalMethod);
300
+ Reflect.defineMetadata("elicitation:enabled", true, originalMethod);
301
+ const strategy = config.strategy || "form";
302
+ Reflect.defineMetadata("elicitation:strategy", strategy, originalMethod);
303
+ descriptor.value = async function(args, meta) {
304
+ const context = {
305
+ args: args || {},
306
+ meta,
307
+ previousAttempts: 0
308
+ };
309
+ if (config.condition && !config.condition(args || {})) {
310
+ return originalMethod.call(this, args, meta);
311
+ }
312
+ let needsElicitation = false;
313
+ let fieldsToCheck = [];
314
+ if (config.builder) {
315
+ const builtConfig = config.builder(context);
316
+ if (builtConfig && typeof builtConfig === "object" && "fields" in builtConfig) {
317
+ fieldsToCheck = builtConfig.fields || [];
318
+ } else if (Array.isArray(builtConfig)) {
319
+ const firstStep = builtConfig[0];
320
+ if (firstStep && firstStep.fields) {
321
+ fieldsToCheck = firstStep.fields;
322
+ }
323
+ }
324
+ } else if (config.fields) {
325
+ fieldsToCheck = config.fields;
326
+ }
327
+ for (const field of fieldsToCheck) {
328
+ if (field.required) {
329
+ const value = args[field.name];
330
+ if (value === void 0 || value === null || value === "") {
331
+ needsElicitation = true;
332
+ break;
333
+ }
334
+ }
335
+ }
336
+ if (needsElicitation) {
337
+ const elicitationRequest = buildElicitationRequestInternal(config, context);
338
+ return elicitationRequest;
339
+ }
340
+ return originalMethod.call(this, args, meta);
341
+ };
342
+ if (descriptor.value && typeof descriptor.value === "function") {
343
+ copyMethodMetadata(originalMethod, descriptor.value);
344
+ }
345
+ };
346
+ }
347
+ __name(Elicitation, "Elicitation");
348
+ function copyMethodMetadata(source, target) {
349
+ const metadataKeys = Reflect.getMetadataKeys(source) || [];
350
+ for (const key of metadataKeys) {
351
+ const value = Reflect.getMetadata(key, source);
352
+ if (value !== void 0) {
353
+ Reflect.defineMetadata(key, value, target);
354
+ }
355
+ }
356
+ }
357
+ __name(copyMethodMetadata, "copyMethodMetadata");
358
+ function buildElicitationRequestInternal(config, context) {
359
+ if (config.builder) {
360
+ const result = config.builder(context);
361
+ if (Array.isArray(result)) {
362
+ const strategy = new MultiStepElicitationStrategy(result);
363
+ return strategy.buildRequest(config, context);
364
+ }
365
+ if (result && typeof result === "object" && "fields" in result) {
366
+ const builtConfig = result;
367
+ const strategy = new FormElicitationStrategy();
368
+ return strategy.buildRequest(builtConfig, context);
369
+ }
370
+ return result;
371
+ }
372
+ const strategyType = config.strategy || "form";
373
+ switch (strategyType) {
374
+ case "form": {
375
+ const strategy = new FormElicitationStrategy();
376
+ return strategy.buildRequest(config, context);
377
+ }
378
+ case "multi-step": {
379
+ if (!config.fields) {
380
+ throw new Error("Multi-step elicitation requires either a builder or fields");
381
+ }
382
+ const steps = [
383
+ {
384
+ title: config.title || "Step 1",
385
+ description: config.description,
386
+ fields: config.fields
387
+ }
388
+ ];
389
+ const strategy = new MultiStepElicitationStrategy(steps);
390
+ return strategy.buildRequest(config, context);
391
+ }
392
+ default:
393
+ throw new Error(`Unsupported elicitation strategy: ${strategyType}`);
394
+ }
395
+ }
396
+ __name(buildElicitationRequestInternal, "buildElicitationRequestInternal");
397
+ function isElicitationEnabled(method) {
398
+ return Reflect.getMetadata("elicitation:enabled", method) === true;
399
+ }
400
+ __name(isElicitationEnabled, "isElicitationEnabled");
401
+ function getElicitationConfig(method) {
402
+ return Reflect.getMetadata("elicitation:config", method);
403
+ }
404
+ __name(getElicitationConfig, "getElicitationConfig");
405
+ function getElicitationStrategy(method) {
406
+ return Reflect.getMetadata("elicitation:strategy", method);
407
+ }
408
+ __name(getElicitationStrategy, "getElicitationStrategy");
409
+ function buildElicitationRequest(method, args, meta) {
410
+ const config = getElicitationConfig(method);
411
+ if (!config) return null;
412
+ const context = {
413
+ args,
414
+ meta,
415
+ previousAttempts: 0
416
+ };
417
+ if (config.condition && !config.condition(args)) {
418
+ return null;
419
+ }
420
+ if (config.builder) {
421
+ const result = config.builder(context);
422
+ if (Array.isArray(result)) {
423
+ const strategy = new MultiStepElicitationStrategy(result);
424
+ return strategy.buildRequest(config, context);
425
+ }
426
+ return result;
427
+ }
428
+ const strategyType = config.strategy || "form";
429
+ switch (strategyType) {
430
+ case "form": {
431
+ const strategy = new FormElicitationStrategy();
432
+ return strategy.buildRequest(config, context);
433
+ }
434
+ case "multi-step": {
435
+ if (!config.fields) {
436
+ throw new Error("Multi-step elicitation requires either a builder or fields");
437
+ }
438
+ const steps = [
439
+ {
440
+ title: config.title || "Step 1",
441
+ description: config.description,
442
+ fields: config.fields
443
+ }
444
+ ];
445
+ const strategy = new MultiStepElicitationStrategy(steps);
446
+ return strategy.buildRequest(config, context);
447
+ }
448
+ default:
449
+ throw new Error(`Unsupported elicitation strategy: ${strategyType}`);
450
+ }
451
+ }
452
+ __name(buildElicitationRequest, "buildElicitationRequest");
453
+
454
+ // src/builders/form-builder.ts
455
+ var ElicitationFormBuilder = class {
456
+ static {
457
+ __name(this, "ElicitationFormBuilder");
458
+ }
459
+ fields = [];
460
+ config = {};
461
+ /**
462
+ * Set the form title
463
+ */
464
+ title(title) {
465
+ this.config.title = title;
466
+ return this;
467
+ }
468
+ /**
469
+ * Set the form description
470
+ */
471
+ description(description) {
472
+ this.config.description = description;
473
+ return this;
474
+ }
475
+ /**
476
+ * Set a condition for when elicitation should occur
477
+ */
478
+ condition(condition) {
479
+ this.config.condition = condition;
480
+ return this;
481
+ }
482
+ /**
483
+ * Add a text field
484
+ */
485
+ addTextField(name, label, options) {
486
+ this.fields.push({
487
+ name,
488
+ label,
489
+ type: "text",
490
+ ...options
491
+ });
492
+ return this;
493
+ }
494
+ /**
495
+ * Add a textarea field
496
+ */
497
+ addTextAreaField(name, label, options) {
498
+ this.fields.push({
499
+ name,
500
+ label,
501
+ type: "textarea",
502
+ ...options
503
+ });
504
+ return this;
505
+ }
506
+ /**
507
+ * Add a number field
508
+ */
509
+ addNumberField(name, label, options) {
510
+ this.fields.push({
511
+ name,
512
+ label,
513
+ type: "number",
514
+ ...options
515
+ });
516
+ return this;
517
+ }
518
+ /**
519
+ * Add a boolean field (checkbox)
520
+ */
521
+ addBooleanField(name, label, options) {
522
+ this.fields.push({
523
+ name,
524
+ label,
525
+ type: "boolean",
526
+ ...options
527
+ });
528
+ return this;
529
+ }
530
+ /**
531
+ * Add a select field (dropdown)
532
+ */
533
+ addSelectField(name, label, options, fieldOptions) {
534
+ this.fields.push({
535
+ name,
536
+ label,
537
+ type: "select",
538
+ options,
539
+ ...fieldOptions
540
+ });
541
+ return this;
542
+ }
543
+ /**
544
+ * Add a multi-select field
545
+ */
546
+ addMultiSelectField(name, label, options, fieldOptions) {
547
+ this.fields.push({
548
+ name,
549
+ label,
550
+ type: "multiselect",
551
+ options,
552
+ ...fieldOptions
553
+ });
554
+ return this;
555
+ }
556
+ /**
557
+ * Add an email field
558
+ */
559
+ addEmailField(name, label, options) {
560
+ this.fields.push({
561
+ name,
562
+ label,
563
+ type: "email",
564
+ ...options
565
+ });
566
+ return this;
567
+ }
568
+ /**
569
+ * Add a URL field
570
+ */
571
+ addUrlField(name, label, options) {
572
+ this.fields.push({
573
+ name,
574
+ label,
575
+ type: "url",
576
+ ...options
577
+ });
578
+ return this;
579
+ }
580
+ /**
581
+ * Add a date field
582
+ */
583
+ addDateField(name, label, options) {
584
+ this.fields.push({
585
+ name,
586
+ label,
587
+ type: "date",
588
+ ...options
589
+ });
590
+ return this;
591
+ }
592
+ /**
593
+ * Add a custom field with full control
594
+ */
595
+ addCustomField(field) {
596
+ this.fields.push(field);
597
+ return this;
598
+ }
599
+ /**
600
+ * Build the final configuration
601
+ */
602
+ build() {
603
+ return {
604
+ ...this.config,
605
+ fields: this.fields,
606
+ strategy: "form"
607
+ };
608
+ }
609
+ };
610
+ var ValidationBuilder = class {
611
+ static {
612
+ __name(this, "ValidationBuilder");
613
+ }
614
+ validation = {};
615
+ min(min) {
616
+ this.validation.min = min;
617
+ return this;
618
+ }
619
+ max(max) {
620
+ this.validation.max = max;
621
+ return this;
622
+ }
623
+ minLength(minLength) {
624
+ this.validation.minLength = minLength;
625
+ return this;
626
+ }
627
+ maxLength(maxLength) {
628
+ this.validation.maxLength = maxLength;
629
+ return this;
630
+ }
631
+ pattern(pattern) {
632
+ this.validation.pattern = pattern;
633
+ return this;
634
+ }
635
+ customValidator(validator) {
636
+ this.validation.customValidator = validator;
637
+ return this;
638
+ }
639
+ errorMessage(message) {
640
+ this.validation.errorMessage = message;
641
+ return this;
642
+ }
643
+ build() {
644
+ return this.validation;
645
+ }
646
+ };
647
+ function validation() {
648
+ return new ValidationBuilder();
649
+ }
650
+ __name(validation, "validation");
651
+ export {
652
+ Elicitation,
653
+ ElicitationError,
654
+ ElicitationFormBuilder,
655
+ ElicitationStrategyBase,
656
+ FormElicitationStrategy,
657
+ MultiStepElicitationStrategy,
658
+ ValidationBuilder,
659
+ buildElicitationRequest,
660
+ checkMissingFields,
661
+ getElicitationConfig,
662
+ getElicitationStrategy,
663
+ isElicitationEnabled,
664
+ validation
665
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@leanmcp/elicitation",
3
+ "version": "0.1.0",
4
+ "description": "Elicitation support for LeanMCP - structured user input collection",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "require": "./dist/index.js",
12
+ "import": "./dist/index.mjs"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md",
18
+ "LICENSE"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsup src/index.ts --format cjs,esm --dts",
22
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23
+ "clean": "rm -rf dist",
24
+ "test": "jest",
25
+ "lint": "eslint src"
26
+ },
27
+ "dependencies": {
28
+ "reflect-metadata": "^0.2.0"
29
+ },
30
+ "peerDependencies": {
31
+ "@leanmcp/core": "*"
32
+ },
33
+ "keywords": [
34
+ "mcp",
35
+ "model-context-protocol",
36
+ "typescript",
37
+ "decorators",
38
+ "elicitation",
39
+ "forms",
40
+ "validation",
41
+ "user-input",
42
+ "leanmcp"
43
+ ],
44
+ "repository": {
45
+ "type": "git",
46
+ "url": "git+https://github.com/LeanMCP/leanmcp-sdk.git",
47
+ "directory": "packages/elicitation"
48
+ },
49
+ "homepage": "https://github.com/LeanMCP/leanmcp-sdk#readme",
50
+ "bugs": {
51
+ "url": "https://github.com/LeanMCP/leanmcp-sdk/issues"
52
+ },
53
+ "author": "LeanMCP <admin@leanmcp.com>",
54
+ "license": "MIT",
55
+ "publishConfig": {
56
+ "access": "public"
57
+ }
58
+ }