Jeremiah Lowin commited on
Commit
c32211c
·
unverified ·
2 Parent(s): c292ebe3f7d213

Merge pull request #1129 from jlowin/expand-empty-params-fix-1128

Browse files
src/fastmcp/server/openapi.py CHANGED
@@ -344,18 +344,28 @@ class OpenAPITool(Tool):
344
  suffixed_name = f"{p.name}__{p.location}"
345
  param_value = None
346
 
 
347
  if (
348
  suffixed_name in arguments
349
- and arguments.get(suffixed_name) is not None
350
- and arguments.get(suffixed_name) != ""
 
 
 
 
351
  ):
352
  param_value = arguments[suffixed_name]
353
- elif (
354
- p.name in arguments
355
- and arguments.get(p.name) is not None
356
- and arguments.get(p.name) != ""
357
- ):
358
- param_value = arguments[p.name]
 
 
 
 
 
359
 
360
  if param_value is not None:
361
  # Handle different parameter styles and types
 
344
  suffixed_name = f"{p.name}__{p.location}"
345
  param_value = None
346
 
347
+ suffixed_value = arguments.get(suffixed_name)
348
  if (
349
  suffixed_name in arguments
350
+ and suffixed_value is not None
351
+ and suffixed_value != ""
352
+ and not (
353
+ isinstance(suffixed_value, list | dict)
354
+ and len(suffixed_value) == 0
355
+ )
356
  ):
357
  param_value = arguments[suffixed_name]
358
+ else:
359
+ name_value = arguments.get(p.name)
360
+ if (
361
+ p.name in arguments
362
+ and name_value is not None
363
+ and name_value != ""
364
+ and not (
365
+ isinstance(name_value, list | dict) and len(name_value) == 0
366
+ )
367
+ ):
368
+ param_value = arguments[p.name]
369
 
370
  if param_value is not None:
371
  # Handle different parameter styles and types
tests/server/openapi/test_openapi_path_parameters.py CHANGED
@@ -448,6 +448,154 @@ async def test_array_query_parameter_exploded_format(mock_client):
448
  )
449
 
450
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  def test_parameter_location_enum_handling():
452
  """Test that ParameterLocation enum values are handled correctly (issue #950)."""
453
  from enum import Enum
 
448
  )
449
 
450
 
451
+ async def test_empty_array_parameter_exclusion(mock_client):
452
+ """Test that empty array parameters are excluded from requests."""
453
+ # Create a route with array query parameter
454
+ route = HTTPRoute(
455
+ path="/search",
456
+ method="GET",
457
+ operation_id="search-operation",
458
+ parameters=[
459
+ ParameterInfo(
460
+ name="tags",
461
+ location="query",
462
+ required=False,
463
+ schema={
464
+ "type": "array",
465
+ "items": {"type": "string"},
466
+ },
467
+ ),
468
+ ParameterInfo(
469
+ name="categories",
470
+ location="query",
471
+ required=False,
472
+ schema={
473
+ "type": "array",
474
+ "items": {"type": "string"},
475
+ },
476
+ ),
477
+ ParameterInfo(
478
+ name="limit",
479
+ location="query",
480
+ required=False,
481
+ schema={"type": "integer"},
482
+ ),
483
+ ],
484
+ )
485
+
486
+ # Create the tool
487
+ tool = OpenAPITool(
488
+ client=mock_client,
489
+ route=route,
490
+ name="search-operation",
491
+ description="Search operation",
492
+ parameters={},
493
+ )
494
+
495
+ # Test with empty array - should be excluded
496
+ await tool.run(
497
+ {
498
+ "tags": [], # Empty array should be excluded
499
+ "categories": ["tech", "news"], # Non-empty array should be included
500
+ "limit": 10, # Non-array param should be included
501
+ }
502
+ )
503
+
504
+ # Check that empty array is excluded, but others are included
505
+ mock_client.request.assert_called_with(
506
+ method="GET",
507
+ url="/search",
508
+ params={
509
+ "categories": ["tech", "news"], # Only non-empty array included
510
+ "limit": 10,
511
+ },
512
+ headers={},
513
+ json=None,
514
+ timeout=None,
515
+ )
516
+
517
+
518
+ async def test_empty_deep_object_parameter_exclusion(mock_client):
519
+ """Test that empty dict parameters with deepObject style are excluded from requests."""
520
+ # Create a route with deepObject query parameter
521
+ route = HTTPRoute(
522
+ path="/filter",
523
+ method="GET",
524
+ operation_id="filter-operation",
525
+ parameters=[
526
+ ParameterInfo(
527
+ name="filters",
528
+ location="query",
529
+ required=False,
530
+ style="deepObject",
531
+ explode=True,
532
+ schema={
533
+ "type": "object",
534
+ "properties": {
535
+ "name": {"type": "string"},
536
+ "age": {"type": "integer"},
537
+ },
538
+ },
539
+ ),
540
+ ParameterInfo(
541
+ name="options",
542
+ location="query",
543
+ required=False,
544
+ style="deepObject",
545
+ explode=True,
546
+ schema={
547
+ "type": "object",
548
+ "properties": {
549
+ "sort": {"type": "string"},
550
+ "order": {"type": "string"},
551
+ },
552
+ },
553
+ ),
554
+ ParameterInfo(
555
+ name="page",
556
+ location="query",
557
+ required=False,
558
+ schema={"type": "integer"},
559
+ ),
560
+ ],
561
+ )
562
+
563
+ # Create the tool
564
+ tool = OpenAPITool(
565
+ client=mock_client,
566
+ route=route,
567
+ name="filter-operation",
568
+ description="Filter operation",
569
+ parameters={},
570
+ )
571
+
572
+ # Test with empty dict - should be excluded
573
+ await tool.run(
574
+ {
575
+ "filters": {}, # Empty dict should be excluded
576
+ "options": {
577
+ "sort": "name",
578
+ "order": "asc",
579
+ }, # Non-empty dict should be included
580
+ "page": 1, # Non-dict param should be included
581
+ }
582
+ )
583
+
584
+ # Check that empty dict is excluded, but others are included
585
+ mock_client.request.assert_called_with(
586
+ method="GET",
587
+ url="/filter",
588
+ params={
589
+ "options[sort]": "name", # Deep object style for non-empty dict
590
+ "options[order]": "asc",
591
+ "page": 1,
592
+ },
593
+ headers={},
594
+ json=None,
595
+ timeout=None,
596
+ )
597
+
598
+
599
  def test_parameter_location_enum_handling():
600
  """Test that ParameterLocation enum values are handled correctly (issue #950)."""
601
  from enum import Enum