@@ -54,6 +54,35 @@ public static string Instructions(Settings settings)
5454 return Intro + string . Concat ( Lines . Where ( l => active . Contains ( l . Key ) ) . Select ( l => l . Value ) ) ;
5555 }
5656
57+ /// <summary>
58+ /// Field values as Claude sends them: an object whose values are text, numbers or booleans, or that object as a JSON
59+ /// string, or "Name=Value; Name2=Value2". A strict Dictionary<string,string> would reject {"Priority": 2} before the
60+ /// tool runs, with no message.
61+ /// </summary>
62+ internal static Dictionary < string , string > ? Fields ( System . Text . Json . JsonElement ? fields )
63+ {
64+ if ( fields is not { } f || f . ValueKind is System . Text . Json . JsonValueKind . Null or System . Text . Json . JsonValueKind . Undefined ) return null ;
65+ if ( f . ValueKind == System . Text . Json . JsonValueKind . String )
66+ {
67+ var s = f . GetString ( ) ?? "" ;
68+ if ( s . TrimStart ( ) . StartsWith ( '{' ) )
69+ try { return Fields ( System . Text . Json . JsonDocument . Parse ( s ) . RootElement . Clone ( ) ) ; }
70+ catch ( System . Text . Json . JsonException ) { throw new ArgumentException ( "fields is not valid JSON; give an object like {\" Priority\" : 2}" ) ; }
71+ return s . Split ( [ ';' , '\n ' ] , StringSplitOptions . RemoveEmptyEntries | StringSplitOptions . TrimEntries )
72+ . Select ( p => p . Split ( '=' , 2 ) ) . Where ( p => p . Length == 2 )
73+ . ToDictionary ( p => p [ 0 ] . Trim ( ) , p => p [ 1 ] . Trim ( ) , StringComparer . OrdinalIgnoreCase ) ;
74+ }
75+ if ( f . ValueKind != System . Text . Json . JsonValueKind . Object ) throw new ArgumentException ( "fields is an object: {\" Field name\" : \" value\" }" ) ;
76+ return f . EnumerateObject ( ) . ToDictionary ( p => p . Name , p => p . Value . ValueKind switch
77+ {
78+ System . Text . Json . JsonValueKind . String => p . Value . GetString ( ) ?? "" ,
79+ System . Text . Json . JsonValueKind . Null => "" ,
80+ System . Text . Json . JsonValueKind . True => "true" ,
81+ System . Text . Json . JsonValueKind . False => "false" ,
82+ _ => p . Value . GetRawText ( ) ,
83+ } , StringComparer . OrdinalIgnoreCase ) ;
84+ }
85+
5786 /// <summary>Set at server start from the settings: end every answer with "[~N tokens]".</summary>
5887 public static bool ShowTokens { get ; set ; }
5988
@@ -89,6 +118,12 @@ internal static async Task<string> Run(string tool, object args, Func<Task<strin
89118 error = true ;
90119 throw new McpException ( "Azure DevOps did not answer in time" ) ;
91120 }
121+ catch ( Exception ex ) when ( ex is not OperationCanceledException and not McpException )
122+ {
123+ // Anything unexpected still says what happened: otherwise the client shows only "An error occurred".
124+ error = true ;
125+ throw new McpException ( $ "{ tool } failed ({ ex . GetType ( ) . Name } ): { ex . Message } ") ;
126+ }
92127 finally
93128 {
94129 Usage . Record ( tool , args , result , sw . ElapsedMilliseconds , error ) ;
@@ -438,7 +473,7 @@ public static Task<string> Create(Lens lens,
438473 string ? area = null ,
439474 [ Description ( "Tags separated by ';'." ) ] string ? tags = null ,
440475 string ? state = null ,
441- [ Description ( "Other fields by name or reference name." ) ] Dictionary < string , string > ? fields = null ,
476+ [ Description ( "Other fields by name or reference name: { \" Priority \" : 2} ." ) ] System . Text . Json . JsonElement ? fields = null ,
442477 [ Description ( "A team template (ado_fields type=… lists them) for the values not given." ) ] string ? template = null ,
443478 string ? project = null ,
444479 string ? connection = null ,
@@ -447,7 +482,7 @@ public static Task<string> Create(Lens lens,
447482 ( ) => WorkQueries . CreateAsync ( lens , connection , project , type , new WriteRequest
448483 {
449484 Title = title , Description = description , AcceptanceCriteria = acceptanceCriteria , AssignedTo = assignedTo ,
450- Parent = parent , Iteration = iteration , Area = area , Tags = tags , State = state , Fields = fields , Template = template ,
485+ Parent = parent , Iteration = iteration , Area = area , Tags = tags , State = state , Fields = McpTools . Fields ( fields ) , Template = template ,
451486 } , ct ) ) ;
452487
453488 [ McpServerTool ( Name = "ado_update" ) ]
@@ -464,7 +499,7 @@ public static Task<string> Update(Lens lens,
464499 [ Description ( "Iteration path, 'current' or 'next'." ) ] string ? iteration = null ,
465500 string ? area = null ,
466501 [ Description ( "'a; b' replaces, '+a; -b' adds and removes." ) ] string ? tags = null ,
467- [ Description ( "Other fields by name or reference name; empty value clears." ) ] Dictionary < string , string > ? fields = null ,
502+ [ Description ( "Other fields by name or reference name; empty value clears." ) ] System . Text . Json . JsonElement ? fields = null ,
468503 [ Description ( "Markdown comment for each item, alone or with changes; @[name or e-mail] notifies a person." ) ] string ? comment = null ,
469504 [ Description ( "Token from the preview." ) ] string ? confirm = null ,
470505 string ? connection = null ,
@@ -473,7 +508,7 @@ public static Task<string> Update(Lens lens,
473508 ( ) => WorkQueries . UpdateAsync ( lens , connection , ids , wiql , new WriteRequest
474509 {
475510 Title = title , Description = description , AcceptanceCriteria = acceptanceCriteria , State = state , Reason = reason ,
476- AssignedTo = assignedTo , Iteration = iteration , Area = area , Tags = tags , Fields = fields ,
511+ AssignedTo = assignedTo , Iteration = iteration , Area = area , Tags = tags , Fields = McpTools . Fields ( fields ) ,
477512 } , comment , confirm , lens . Settings . ConfirmBulkAbove , ct ) ) ;
478513
479514 [ McpServerTool ( Name = "ado_comment" ) ]
@@ -580,7 +615,7 @@ public static class RecipeTools
580615 public static Task < string > Recipe ( Lens lens , string action ,
581616 string ? name = null ,
582617 [ Description ( "For save: the recipe JSON." ) ] string ? recipe = null ,
583- [ Description ( "For run: parameter values." ) ] Dictionary < string , string > ? @params = null ,
618+ [ Description ( "For run: parameter values." ) ] System . Text . Json . JsonElement ? @params = null ,
584619 string ? confirm = null ,
585620 string ? connection = null ,
586621 CancellationToken ct = default )
@@ -593,7 +628,7 @@ public static Task<string> Recipe(Lens lens, string action,
593628 "show" => Recipes . Show ( lens , Need ( name , "name" ) ) ,
594629 "save" => Recipes . Save ( lens , Need ( name , "name" ) , Need ( recipe , "recipe" ) ) ,
595630 "delete" => Recipes . Delete ( lens , Need ( name , "name" ) ) ,
596- "run" => await Recipes . RunAsync ( lens , connection , Need ( name , "name" ) , @params , confirm , yes : false , ct ) ,
631+ "run" => await Recipes . RunAsync ( lens , connection , Need ( name , "name" ) , McpTools . Fields ( @params ) , confirm , yes : false , ct ) ,
597632 _ => throw new ArgumentException ( "action is list, show, save, run or delete" ) ,
598633 } ;
599634 } ) ;
0 commit comments