Skip to content

Commit f715df1

Browse files
author
LittleCoinCoin
committed
feat(mcp): implement Gemini dual-transport validation
Add comprehensive validation for Gemini's dual-transport capability supporting both SSE (with 'url' field) and HTTP (with 'httpUrl' field) transports. Key changes: - Override parent transport validator in MCPServerConfigGemini - Validate mutual exclusion of 'url' and 'httpUrl' fields - Support type inference from transport fields - Add 3 comprehensive validation tests Gemini now supports: - stdio transport with 'command' field - sse transport with 'url' field - http transport with 'httpUrl' field Fixes: Issue 3 - Gemini dual-transport validation
1 parent 1933351 commit f715df1

File tree

3 files changed

+91
-3
lines changed

3 files changed

+91
-3
lines changed

hatch/mcp_host_config/models.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,14 @@ class MCPServerConfigBase(BaseModel):
360360

361361
@model_validator(mode='after')
362362
def validate_transport(self) -> 'MCPServerConfigBase':
363-
"""Validate transport configuration using type field."""
363+
"""Validate transport configuration using type field.
364+
365+
Note: Gemini subclass overrides this with dual-transport support.
366+
"""
367+
# Skip validation for Gemini which has its own dual-transport validator
368+
if self.__class__.__name__ == 'MCPServerConfigGemini':
369+
return self
370+
364371
# Check mutual exclusion - command and url cannot both be set
365372
if self.command is not None and self.url is not None:
366373
raise ValueError(
@@ -413,6 +420,45 @@ class MCPServerConfigGemini(MCPServerConfigBase):
413420
oauth_audiences: Optional[List[str]] = Field(None, description="OAuth audiences")
414421
authProviderType: Optional[str] = Field(None, description="Authentication provider type")
415422

423+
@model_validator(mode='after')
424+
def validate_gemini_dual_transport(self):
425+
"""Override transport validation to support Gemini's dual-transport capability.
426+
427+
Gemini supports both:
428+
- SSE transport with 'url' field
429+
- HTTP transport with 'httpUrl' field
430+
431+
Validates that:
432+
1. Either url or httpUrl is provided (not both)
433+
2. Type field matches the transport being used
434+
"""
435+
# Check if both url and httpUrl are provided
436+
if self.url is not None and self.httpUrl is not None:
437+
raise ValueError("Cannot specify both 'url' and 'httpUrl' - choose one transport")
438+
439+
# Validate based on type
440+
if self.type == "stdio":
441+
if not self.command:
442+
raise ValueError("'command' is required for stdio transport")
443+
elif self.type == "sse":
444+
if not self.url:
445+
raise ValueError("'url' is required for sse transport")
446+
elif self.type == "http":
447+
if not self.httpUrl:
448+
raise ValueError("'httpUrl' is required for http transport")
449+
elif self.type is None:
450+
# Infer type from fields if not specified
451+
if self.command:
452+
self.type = "stdio"
453+
elif self.url:
454+
self.type = "sse" # default to sse for url
455+
elif self.httpUrl:
456+
self.type = "http" # http for httpUrl
457+
else:
458+
raise ValueError("Either 'command', 'url', or 'httpUrl' must be provided")
459+
460+
return self
461+
416462
@classmethod
417463
def from_omni(cls, omni: 'MCPServerConfigOmni') -> 'MCPServerConfigGemini':
418464
"""Convert Omni model to Gemini-specific model using Pydantic APIs."""

tests/test_mcp_cli_partial_updates.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,9 +626,9 @@ def test_partial_update_end_to_end_switch_type(self):
626626
with patch('hatch.cli_hatch.generate_conversion_report') as mock_report:
627627
mock_report.return_value = MagicMock()
628628

629-
# Execute: Switch to URL-based
629+
# Execute: Switch to URL-based (use gemini which supports URL)
630630
result = handle_mcp_configure(
631-
host="claude-desktop",
631+
host="gemini",
632632
server_name="test-server",
633633
command=None,
634634
args=None,

tests/test_mcp_pydantic_architecture_v4.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,48 @@ def test_claude_from_omni_with_universal_fields(self):
556556
self.assertEqual(claude.env["API_KEY"], "test")
557557

558558

559+
class TestGeminiDualTransport(unittest.TestCase):
560+
"""Test suite for Gemini dual-transport validation (Issue 3)."""
561+
562+
@regression_test
563+
def test_gemini_sse_transport_with_url(self):
564+
"""Test Gemini SSE transport uses url field."""
565+
config = MCPServerConfigGemini(
566+
name="gemini-server",
567+
type="sse",
568+
url="https://api.example.com/mcp"
569+
)
570+
571+
self.assertEqual(config.type, "sse")
572+
self.assertEqual(config.url, "https://api.example.com/mcp")
573+
self.assertIsNone(config.httpUrl)
574+
575+
@regression_test
576+
def test_gemini_http_transport_with_httpUrl(self):
577+
"""Test Gemini HTTP transport uses httpUrl field."""
578+
config = MCPServerConfigGemini(
579+
name="gemini-server",
580+
type="http",
581+
httpUrl="https://api.example.com/mcp"
582+
)
583+
584+
self.assertEqual(config.type, "http")
585+
self.assertEqual(config.httpUrl, "https://api.example.com/mcp")
586+
self.assertIsNone(config.url)
587+
588+
@regression_test
589+
def test_gemini_mutual_exclusion_url_and_httpUrl(self):
590+
"""Test Gemini rejects both url and httpUrl simultaneously."""
591+
with self.assertRaises(ValidationError) as context:
592+
MCPServerConfigGemini(
593+
name="gemini-server",
594+
url="https://api.example.com/sse",
595+
httpUrl="https://api.example.com/http"
596+
)
597+
598+
self.assertIn("Cannot specify both 'url' and 'httpUrl'", str(context.exception))
599+
600+
559601
if __name__ == '__main__':
560602
unittest.main()
561603

0 commit comments

Comments
 (0)