@@ -7,6 +7,7 @@ import { Action } from '../../../../base/common/actions.js';
77import { assertNever } from '../../../../base/common/assert.js' ;
88import { CancellationToken } from '../../../../base/common/cancellation.js' ;
99import { DisposableStore } from '../../../../base/common/lifecycle.js' ;
10+ import { isDefined } from '../../../../base/common/types.js' ;
1011import { localize } from '../../../../nls.js' ;
1112import { INotificationService , Severity } from '../../../../platform/notification/common/notification.js' ;
1213import { IQuickInputService , IQuickPick , IQuickPickItem } from '../../../../platform/quickinput/common/quickInput.js' ;
@@ -20,6 +21,31 @@ import { MCP } from '../common/modelContextProtocol.js';
2021
2122const noneItem : IQuickPickItem = { id : undefined , label : localize ( 'mcp.elicit.enum.none' , 'None' ) , description : localize ( 'mcp.elicit.enum.none.description' , 'No selection' ) , alwaysShow : true } ;
2223
24+ function isLegacyTitledEnumSchema ( schema : MCP . PrimitiveSchemaDefinition ) : schema is MCP . LegacyTitledEnumSchema & { enumNames : string [ ] } {
25+ const cast = schema as MCP . LegacyTitledEnumSchema ;
26+ return cast . type === 'string' && Array . isArray ( cast . enum ) && Array . isArray ( cast . enumNames ) ;
27+ }
28+
29+ function isUntitledEnumSchema ( schema : MCP . PrimitiveSchemaDefinition ) : schema is MCP . LegacyTitledEnumSchema | MCP . UntitledSingleSelectEnumSchema {
30+ const cast = schema as MCP . LegacyTitledEnumSchema | MCP . UntitledSingleSelectEnumSchema ;
31+ return cast . type === 'string' && Array . isArray ( cast . enum ) ;
32+ }
33+
34+ function isTitledSingleEnumSchema ( schema : MCP . PrimitiveSchemaDefinition ) : schema is MCP . TitledSingleSelectEnumSchema {
35+ const cast = schema as MCP . TitledSingleSelectEnumSchema ;
36+ return cast . type === 'string' && Array . isArray ( cast . oneOf ) ;
37+ }
38+
39+ function isUntitledMultiEnumSchema ( schema : MCP . PrimitiveSchemaDefinition ) : schema is MCP . UntitledMultiSelectEnumSchema {
40+ const cast = schema as MCP . UntitledMultiSelectEnumSchema ;
41+ return cast . type === 'array' && ! ! cast . items ?. enum ;
42+ }
43+
44+ function isTitledMultiEnumSchema ( schema : MCP . PrimitiveSchemaDefinition ) : schema is MCP . TitledMultiSelectEnumSchema {
45+ const cast = schema as MCP . TitledMultiSelectEnumSchema ;
46+ return cast . type === 'array' && ! ! cast . items ?. anyOf ;
47+ }
48+
2349export class McpElicitationService implements IMcpElicitationService {
2450 declare readonly _serviceBrand : undefined ;
2551
@@ -82,7 +108,7 @@ export class McpElicitationService implements IMcpElicitationService {
82108 try {
83109 const properties = Object . entries ( elicitation . requestedSchema . properties ) ;
84110 const requiredFields = new Set ( elicitation . requestedSchema . required || [ ] ) ;
85- const results : Record < string , string | number | boolean > = { } ;
111+ const results : Record < string , string | number | boolean | string [ ] > = { } ;
86112 const backSnapshots : { value : string ; validationMessage ?: string } [ ] = [ ] ;
87113
88114 quickPick . title = elicitation . message ;
@@ -102,12 +128,20 @@ export class McpElicitationService implements IMcpElicitationService {
102128 quickPick . validationMessage = '' ;
103129 quickPick . buttons = i > 0 ? [ this . _quickInputService . backButton ] : [ ] ;
104130
105- let result : { type : 'value' ; value : string | number | boolean | undefined } | { type : 'back' } | { type : 'cancel' } ;
131+ let result : { type : 'value' ; value : string | number | boolean | undefined | string [ ] } | { type : 'back' } | { type : 'cancel' } ;
106132 if ( schema . type === 'boolean' ) {
107- result = await this . _handleEnumField ( quickPick , { ... schema , type : 'string' , enum : [ 'true' , 'false' ] , default : schema . default ? String ( schema . default ) : undefined } , isRequired , store , token ) ;
133+ result = await this . _handleEnumField ( quickPick , { enum : [ { const : 'true' } , { const : 'false' } ] , default : schema . default ? String ( schema . default ) : undefined } , isRequired , store , token ) ;
108134 if ( result . type === 'value' ) { result . value = result . value === 'true' ? true : false ; }
109- } else if ( schema . type === 'string' && 'enum' in schema ) {
110- result = await this . _handleEnumField ( quickPick , schema , isRequired , store , token ) ;
135+ } else if ( isLegacyTitledEnumSchema ( schema ) ) {
136+ result = await this . _handleEnumField ( quickPick , { enum : schema . enum . map ( ( v , i ) => ( { const : v , title : schema . enumNames [ i ] } ) ) , default : schema . default } , isRequired , store , token ) ;
137+ } else if ( isUntitledEnumSchema ( schema ) ) {
138+ result = await this . _handleEnumField ( quickPick , { enum : schema . enum . map ( v => ( { const : v } ) ) , default : schema . default } , isRequired , store , token ) ;
139+ } else if ( isTitledSingleEnumSchema ( schema ) ) {
140+ result = await this . _handleEnumField ( quickPick , { enum : schema . oneOf , default : schema . default } , isRequired , store , token ) ;
141+ } else if ( isTitledMultiEnumSchema ( schema ) ) {
142+ result = await this . _handleMultiEnumField ( quickPick , { enum : schema . items . anyOf , default : schema . default } , isRequired , store , token ) ;
143+ } else if ( isUntitledMultiEnumSchema ( schema ) ) {
144+ result = await this . _handleMultiEnumField ( quickPick , { enum : schema . items . enum . map ( v => ( { const : v } ) ) , default : schema . default } , isRequired , store , token ) ;
111145 } else {
112146 result = await this . _handleInputField ( quickPick , schema , isRequired , store , token ) ;
113147 if ( result . type === 'value' && ( schema . type === 'number' || schema . type === 'integer' ) ) {
@@ -152,23 +186,23 @@ export class McpElicitationService implements IMcpElicitationService {
152186
153187 private async _handleEnumField (
154188 quickPick : IQuickPick < IQuickPickItem > ,
155- schema : MCP . EnumSchema ,
189+ schema : { default ?: string ; enum : { const : string ; title ?: string } [ ] } ,
156190 required : boolean ,
157191 store : DisposableStore ,
158192 token : CancellationToken
159193 ) {
160- const items : IQuickPickItem [ ] = schema . enum . map ( ( value , index ) => ( {
194+ const items : IQuickPickItem [ ] = schema . enum . map ( ( { const : value , title } ) => ( {
161195 id : value ,
162196 label : value ,
163- description : schema . enumNames ?. [ index ] ,
197+ description : title ,
164198 } ) ) ;
165199
166200 if ( ! required ) {
167201 items . push ( noneItem ) ;
168202 }
169203
170- quickPick . items = items ;
171204 quickPick . canSelectMany = false ;
205+ quickPick . items = items ;
172206 if ( schema . default !== undefined ) {
173207 quickPick . activeItems = items . filter ( item => item . id === schema . default ) ;
174208 }
@@ -188,6 +222,45 @@ export class McpElicitationService implements IMcpElicitationService {
188222 } ) ;
189223 }
190224
225+ private async _handleMultiEnumField (
226+ quickPick : IQuickPick < IQuickPickItem > ,
227+ schema : { default ?: string [ ] ; enum : { const : string ; title ?: string } [ ] } ,
228+ required : boolean ,
229+ store : DisposableStore ,
230+ token : CancellationToken
231+ ) {
232+ const items : IQuickPickItem [ ] = schema . enum . map ( ( { const : value , title } ) => ( {
233+ id : value ,
234+ label : value ,
235+ description : title ,
236+ picked : ! ! schema . default ?. includes ( value ) ,
237+ pickable : true ,
238+ } ) ) ;
239+
240+ if ( ! required ) {
241+ items . push ( noneItem ) ;
242+ }
243+
244+ quickPick . canSelectMany = true ;
245+ quickPick . items = items ;
246+
247+ return new Promise < { type : 'value' ; value : string [ ] | undefined } | { type : 'back' } | { type : 'cancel' } > ( resolve => {
248+ store . add ( token . onCancellationRequested ( ( ) => resolve ( { type : 'cancel' } ) ) ) ;
249+ store . add ( quickPick . onDidAccept ( ( ) => {
250+ const selected = quickPick . selectedItems [ 0 ] ;
251+ if ( selected . id === undefined ) {
252+ resolve ( { type : 'value' , value : undefined } ) ;
253+ } else {
254+ resolve ( { type : 'value' , value : quickPick . selectedItems . map ( i => i . id ) . filter ( isDefined ) } ) ;
255+ }
256+ } ) ) ;
257+ store . add ( quickPick . onDidTriggerButton ( ( ) => resolve ( { type : 'back' } ) ) ) ;
258+ store . add ( quickPick . onDidHide ( ( ) => resolve ( { type : 'cancel' } ) ) ) ;
259+
260+ quickPick . show ( ) ;
261+ } ) ;
262+ }
263+
191264 private async _handleInputField (
192265 quickPick : IQuickPick < IQuickPickItem > ,
193266 schema : MCP . NumberSchema | MCP . StringSchema ,
0 commit comments