Skip to content

Commit 077a66f

Browse files
committed
Add region utils
1 parent 273a37e commit 077a66f

6 files changed

Lines changed: 93 additions & 9 deletions

File tree

aws_mcp_proxy/server.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from aws_mcp_proxy.utils import (
3232
create_transport_with_sigv4,
3333
determine_service_name,
34+
determine_aws_region,
3435
)
3536
from fastmcp.server.server import FastMCP
3637
from typing import Any
@@ -46,9 +47,11 @@ async def setup_mcp_mode(mcp: FastMCP, args) -> None:
4647
# Validate and determine service
4748
service = determine_service_name(args.endpoint, args.service)
4849

49-
# Get profile and region
50+
# Validate and determine region
51+
region = determine_aws_region(args.endpoint, args.region)
52+
53+
# Get profile
5054
profile = args.profile
51-
region = args.region
5255

5356
# Log server configuration
5457
logger.info(
@@ -57,7 +60,7 @@ async def setup_mcp_mode(mcp: FastMCP, args) -> None:
5760
logger.info('Running in MCP mode')
5861

5962
# Create transport with SigV4 authentication
60-
transport = create_transport_with_sigv4(args.endpoint, service, profile)
63+
transport = create_transport_with_sigv4(args.endpoint, service, profile, region)
6164

6265
# Create proxy with the transport
6366
proxy = FastMCP.as_proxy(transport)
@@ -104,7 +107,6 @@ def parse_args():
104107
parser.add_argument(
105108
'--region',
106109
help='AWS region to use (uses AWS_REGION if not provided)',
107-
default=os.getenv('AWS_REGION', 'us-east-1'),
108110
)
109111

110112
parser.add_argument(

aws_mcp_proxy/sigv4_helper.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ def create_sigv4_client(
190190
profile: Optional[str] = None,
191191
region: Optional[str] = None,
192192
headers: Optional[Dict[str, str]] = None,
193-
auth: Optional[httpx.Auth] = None,
194193
**kwargs: Any,
195194
) -> httpx.AsyncClient:
196195
"""Create an httpx.AsyncClient with SigV4 authentication.

aws_mcp_proxy/utils.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424

2525
def create_transport_with_sigv4(
26-
url: str, service: str, profile: Optional[str] = None
26+
url: str, service: str, profile: Optional[str] = None, region: Optional[str] = None,
2727
) -> StreamableHttpTransport:
2828
"""Create a StreamableHttpTransport with SigV4 authentication.
2929
@@ -42,7 +42,7 @@ def client_factory(
4242
auth: Optional[httpx.Auth] = None,
4343
) -> httpx.AsyncClient:
4444
return create_sigv4_client(
45-
service=service, profile=profile, headers=headers, timeout=timeout, auth=auth
45+
service=service, profile=profile, region=region, headers=headers, timeout=timeout, auth=auth
4646
)
4747

4848
return StreamableHttpTransport(
@@ -81,3 +81,34 @@ def determine_service_name(endpoint: str, service: Optional[str] = None) -> str:
8181
'Please provide the service name explicitly using --service argument.'
8282
)
8383
return determined_service
84+
85+
def determine_aws_region(endpoint: str, region: Optional[str] = None) -> str:
86+
"""Validate and determine the AWS region.
87+
88+
Args:
89+
endpoint: The endpoint URL
90+
region: Optional region name
91+
92+
Returns:
93+
Validated AWS region
94+
95+
Raises:
96+
ValueError: If region cannot be determined
97+
"""
98+
if region:
99+
return region
100+
101+
# Parse AWS region from endpoint URL
102+
parsed = urlparse(endpoint)
103+
hostname = parsed.hostname or ''
104+
105+
# Extract region name (pattern: service.region.api.aws or service-name.region.api.aws)
106+
region_match = re.search(r'\.([a-z0-9-]+)\.api\.aws', hostname)
107+
determined_region = region_match.group(1) if region_match else None
108+
109+
if not determined_region:
110+
raise ValueError(
111+
f"Could not determine AWS region from endpoint '{endpoint}'. "
112+
'Please provide the region explicitly using --region argument.'
113+
)
114+
return determined_region

tests/test_server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ async def test_setup_mcp_mode(
3636
mock_args = Mock()
3737
mock_args.endpoint = 'https://test.example.com'
3838
mock_args.service = 'test-service'
39+
mock_args.region = 'us-east-1'
3940
mock_args.profile = None
4041
mock_args.allow_write = False
4142

@@ -71,6 +72,7 @@ async def test_setup_mcp_mode_with_tools(
7172
mock_args = Mock()
7273
mock_args.endpoint = 'https://test.example.com'
7374
mock_args.service = 'test-service'
75+
mock_args.region = 'us-east-1'
7476
mock_args.profile = None
7577
mock_args.allow_write = False
7678

@@ -106,6 +108,7 @@ async def test_setup_mcp_mode_tool_registration_error(
106108
mock_args = Mock()
107109
mock_args.endpoint = 'https://test.example.com'
108110
mock_args.service = 'test-service'
111+
mock_args.region = 'us-east-1'
109112
mock_args.profile = None
110113
mock_args.allow_write = False
111114

tests/test_sigv4_helper.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ class TestCreateSigv4Auth:
226226
"""Test cases for the create_sigv4_auth function."""
227227

228228
@patch('aws_mcp_proxy.sigv4_helper.create_aws_session')
229+
# @patch.dict(os.environ, {"AWS_REGION": ""})
229230
def test_create_sigv4_auth_default(self, mock_create_session):
230231
"""Test creating SigV4 auth with default parameters."""
231232
# Mock session and credentials
@@ -237,6 +238,9 @@ def test_create_sigv4_auth_default(self, mock_create_session):
237238
mock_session.get_credentials.return_value = mock_credentials
238239
mock_create_session.return_value = mock_session
239240

241+
# Remove AWS_REGION to check default
242+
del os.environ["AWS_REGION"]
243+
240244
# Test auth creation
241245
result = create_sigv4_auth('test-service')
242246

tests/test_utils.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from aws_mcp_proxy.utils import (
1919
create_transport_with_sigv4,
2020
determine_service_name,
21+
determine_aws_region,
2122
)
2223
from fastmcp.client.transports import StreamableHttpTransport
2324
from unittest.mock import MagicMock, patch
@@ -35,8 +36,9 @@ def test_create_transport_with_sigv4(self, mock_create_sigv4_client):
3536
url = 'https://eks-mcp.us-west-2.api.aws/mcp'
3637
service = 'eks-mcp'
3738
profile = 'test-profile'
39+
region = 'us-east-1'
3840

39-
result = create_transport_with_sigv4(url, service, profile)
41+
result = create_transport_with_sigv4(url, service, profile, region)
4042

4143
# Verify result is StreamableHttpTransport
4244
assert isinstance(result, StreamableHttpTransport)
@@ -54,6 +56,7 @@ def test_create_transport_with_sigv4(self, mock_create_sigv4_client):
5456
mock_create_sigv4_client.assert_called_once_with(
5557
service=service,
5658
profile=profile,
59+
region=region,
5760
headers={'test': 'header'},
5861
timeout=Timeout(30.0),
5962
auth=None,
@@ -77,7 +80,7 @@ def test_create_transport_with_sigv4_no_profile(self, mock_create_sigv4_client):
7780
factory(headers=None, timeout=None, auth=None)
7881

7982
mock_create_sigv4_client.assert_called_once_with(
80-
service=service, profile=None, headers=None, timeout=None, auth=None
83+
service=service, profile=None, region=None, headers=None, timeout=None, auth=None
8184
)
8285
else:
8386
# If we can't access the factory directly, just verify the transport was created
@@ -144,3 +147,45 @@ def test_validate_service_name_invalid_url_failure(self):
144147
assert 'Could not determine AWS service name' in str(exc_info.value)
145148
assert endpoint in str(exc_info.value)
146149
assert '--service argument' in str(exc_info.value)
150+
151+
152+
class TestDetermineRegion:
153+
"""Test cases for determine_aws_region function."""
154+
155+
def test_determine_region_with_region(self):
156+
"""Test determination when region is provided."""
157+
endpoint = 'https://mcp.us-east-1.api.aws/mcp'
158+
region = 'custom-region'
159+
160+
result = determine_aws_region(endpoint, region)
161+
162+
assert result == region
163+
164+
def test_determine_region_without_region_success(self):
165+
"""Test determination when region is not provided but can be parsed."""
166+
endpoint = 'https://mcp.us-east-1.api.aws/mcp'
167+
expected_region = 'us-east-1'
168+
169+
result = determine_aws_region(endpoint)
170+
171+
assert result == expected_region
172+
173+
def test_determine_region_with_complex_service_name(self):
174+
"""Test parsing region from endpoint with complex service name."""
175+
endpoint = 'https://eks-mcp-beta.us-west-2.api.aws/mcp'
176+
expected_region = 'us-west-2'
177+
178+
result = determine_aws_region(endpoint)
179+
180+
assert result == expected_region
181+
182+
def test_determine_region_without_region_failure(self):
183+
"""Test determination when region cannot be determined."""
184+
endpoint = 'https://service.example.com'
185+
186+
with pytest.raises(ValueError) as exc_info:
187+
determine_aws_region(endpoint)
188+
189+
assert 'Could not determine AWS region' in str(exc_info.value)
190+
assert endpoint in str(exc_info.value)
191+
assert '--region argument' in str(exc_info.value)

0 commit comments

Comments
 (0)