@@ -332,7 +332,7 @@ fn tool_definitions() -> Vec<Value> {
332332 & p. name ,
333333 json ! ( {
334334 "type" : registry:: param_type_to_json_type( p. param_type) ,
335- "description" : p . description ,
335+ "description" : mcp_param_description ( ep , p ) ,
336336 } ) ,
337337 ) ;
338338 if p. required && !required. contains ( & p. name ) {
@@ -390,6 +390,40 @@ fn tool_definitions() -> Vec<Value> {
390390 tools
391391}
392392
393+ /// Return the LLM-facing MCP parameter description for a registry endpoint.
394+ ///
395+ /// Most parameters can use the shared registry wording directly. A small set of
396+ /// option bulk-query parameters benefit from MCP-specific clarification because
397+ /// the MCP transport uses `"0"` as the wildcard sentinel instead of REST's
398+ /// `*`, and `strike_range` only filters an already-bulk selection.
399+ fn mcp_param_description ( ep : & registry:: EndpointMeta , param : & registry:: ParamMeta ) -> String {
400+ if ep. category == "option" {
401+ match param. name {
402+ "strike" => {
403+ return format ! (
404+ "{}. Use \" 0\" for wildcard/bulk strike selection on endpoints that support bulk option queries." ,
405+ param. description
406+ ) ;
407+ }
408+ "expiration" => {
409+ return format ! (
410+ "{}. Use \" 0\" for wildcard/bulk expiration selection on endpoints that support bulk option queries." ,
411+ param. description
412+ ) ;
413+ }
414+ "strike_range" => {
415+ return format ! (
416+ "{}. Filters a wildcard/bulk option selection around spot/ATM; it does not expand a pinned strike." ,
417+ param. description
418+ ) ;
419+ }
420+ _ => { }
421+ }
422+ }
423+
424+ param. description . to_string ( )
425+ }
426+
393427fn negotiate_protocol_version ( client_version : Option < & str > ) -> & ' static str {
394428 client_version
395429 . and_then ( |version| {
@@ -1204,6 +1238,44 @@ mod tests {
12041238 }
12051239 }
12061240
1241+ #[ test]
1242+ fn tool_schemas_clarify_option_bulk_wildcards_for_llm_consumers ( ) {
1243+ let tool = tool_definitions ( )
1244+ . into_iter ( )
1245+ . find ( |tool| {
1246+ tool. get ( "name" )
1247+ . and_then ( |value : & Value | value. as_str ( ) )
1248+ == Some ( "option_history_greeks_eod" )
1249+ } )
1250+ . expect ( "option_history_greeks_eod tool should exist" ) ;
1251+
1252+ let strike = tool
1253+ . pointer ( [ "inputSchema" , "properties" , "strike" , "description" ] )
1254+ . and_then ( |value| value. as_str ( ) )
1255+ . expect ( "strike description should exist" ) ;
1256+ let expiration = tool
1257+ . pointer ( [ "inputSchema" , "properties" , "expiration" , "description" ] )
1258+ . and_then ( |value| value. as_str ( ) )
1259+ . expect ( "expiration description should exist" ) ;
1260+ let strike_range = tool
1261+ . pointer ( [ "inputSchema" , "properties" , "strike_range" , "description" ] )
1262+ . and_then ( |value| value. as_str ( ) )
1263+ . expect ( "strike_range description should exist" ) ;
1264+
1265+ assert ! (
1266+ strike. contains( "\" 0\" for wildcard/bulk strike selection" ) ,
1267+ "strike description should explain MCP wildcard strike semantics: {strike}"
1268+ ) ;
1269+ assert ! (
1270+ expiration. contains( "\" 0\" for wildcard/bulk expiration selection" ) ,
1271+ "expiration description should explain MCP wildcard expiration semantics: {expiration}"
1272+ ) ;
1273+ assert ! (
1274+ strike_range. contains( "does not expand a pinned strike" ) ,
1275+ "strike_range description should explain wildcard-only filtering semantics: {strike_range}"
1276+ ) ;
1277+ }
1278+
12071279 #[ test]
12081280 fn optional_i32_args_reject_out_of_range_values ( ) {
12091281 let args = convert_endpoint_args ( & sonic_rs:: json!( {
0 commit comments