Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ docker build -t mcp-proxy-for-aws .
|----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------|--- |
| `endpoint` | MCP endpoint URL (e.g., `https://your-service.us-east-1.amazonaws.com/mcp`) | N/A |Yes |
| --- | --- | --- |--- |
| `--service` | AWS service name for SigV4 signing | Inferred from endpoint if not provided |No |
| `--service` | AWS service name for SigV4 signing, if omitted we try to infer this from the url | Inferred from endpoint if not provided |No |
| `--profile` | AWS profile for AWS credentials to use | Uses `AWS_PROFILE` environment variable if not set |No |
| `--region` | AWS region to use | Uses `AWS_REGION` environment variable if not set, defaults to `us-east-1` |No |
| `--metadata` | Metadata to inject into MCP requests as key=value pairs (e.g., `--metadata KEY1=value1 KEY2=value2`) | `AWS_REGION` is automatically injected based on `--region` if not provided |No |
Expand Down Expand Up @@ -279,6 +279,15 @@ uv sync

---

## Troubleshooting

### Handling `Authentication error - Invalid credentials`
We try to autodetect the service from the url, sometimes this fails, ensure that `--service` is set correctly to the
service you are attempting to connect to.
Otherwise the SigV4 signing will not be able to be verified by the service you connect to, resulting in this error.
Also ensure that you have valid IAM credentials on your machine before retrying.


## Development & Contributing

For development setup, testing, and contribution guidelines, see:
Expand Down
60 changes: 41 additions & 19 deletions mcp_proxy_for_aws/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@
import httpx
import logging
import os
import re
from fastmcp.client.transports import StreamableHttpTransport
from mcp_proxy_for_aws.sigv4_helper import create_sigv4_client
from typing import Any, Dict, Optional
from typing import Any, Dict, Optional, Tuple
from urllib.parse import urlparse


Expand Down Expand Up @@ -72,34 +71,61 @@ def client_factory(
)


def get_service_name_and_region_from_endpoint(endpoint: str) -> Tuple[str, str]:
"""Extract service name and region from an endpoint URL.

Args:
endpoint: The endpoint URL to parse

Returns:
Tuple of (service_name, region). Either value may be empty string if not found.

Notes:
- Matches bedrock-agentcore endpoints (gateway and runtime)
- Matches AWS API Gateway endpoints (service.region.api.aws)
- Falls back to extracting first hostname segment as service name
"""
# Parse AWS service from endpoint URL
parsed = urlparse(endpoint)
hostname = parsed.hostname or ''
match hostname.split('.'):
case [*_, 'bedrock-agentcore', region, 'amazonaws', 'com']:
return 'bedrock-agentcore', region # gateway and runtime
case [service, region, 'api', 'aws']:
return service, region
case [service, *_]:
# Fallback: extract first segment as service name
return service, ''
case _:
logger.warning('Could not parse endpoint, no hostname found')
return '', ''


def determine_service_name(endpoint: str, service: Optional[str] = None) -> str:
"""Validate and determine the service name.
"""Validate and determine the service name and possibly region from an endpoint.

Args:
endpoint: The endpoint URL
service: Optional service name

Returns:
Validated service name
Validated region

Raises:
ValueError: If service cannot be determined
"""
if service:
return service

# Parse AWS service from endpoint URL
parsed = urlparse(endpoint)
hostname = parsed.hostname or ''

# Extract service name (first part before first dot or dash)
service_match = re.match(r'^([^.]+)', hostname)
determined_service = service_match.group(1) if service_match else None
logger.info('Resolving service name')
endpoint_service, _ = get_service_name_and_region_from_endpoint(endpoint)
determined_service = service or endpoint_service

if not determined_service:
raise ValueError(
f"Could not determine AWS service name from endpoint '{endpoint}'. "
'Please provide the service name explicitly using --service argument.'
f"Could not determine AWS service name and region from endpoint '{endpoint}' and they were not provided."
'Please provide the service name explicitly using --service argument and the region via --region argument.'
)
return determined_service

Expand All @@ -122,14 +148,10 @@ def determine_aws_region(endpoint: str, region: Optional[str]) -> str:
return region

# Parse AWS region from endpoint URL
parsed = urlparse(endpoint)
hostname = parsed.hostname or ''

# Extract region name (pattern: service.region.api.aws or service-name.region.api.aws)
region_match = re.search(r'\.([a-z0-9-]+)\.api\.aws', hostname)
if region_match:
_, endpoint_region = get_service_name_and_region_from_endpoint(endpoint)
if endpoint_region:
logger.debug('Region determined through endpoint URL')
return region_match.group(1)
return endpoint_region

environment_region = os.getenv('AWS_REGION')
if environment_region:
Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ def test_validate_service_name_service_parsing_with_dot(self):
result = determine_service_name(endpoint)
assert result == 'service'

def test_validate_service_name_bedrock_agentcore(self):
"""Test parsing service name for bedrock-agentcore endpoints."""
# Test various bedrock-agentcore endpoint formats
test_cases = [
'https://my-agent.gateway.bedrock-agentcore.us-west-2.amazonaws.com', # Clean gateway
'https://bedrock-agentcore.us-east-1.amazonaws.com', # Clean runtime
'https://bedrock-agentcore.us-east-1.amazonaws.com/runtimes/arn%3Aaws%3Abedrock-agentcore%3Aus-east-1%3A216123456714%3Aruntime%2Fhosted_agent_99wdf-hYKYrgAHVr/invocations',
'https://gateway-quick-start-242206-rsdehprct2.gateway.bedrock-agentcore.eu-central-1.amazonaws.com/mcp',
]

for endpoint in test_cases:
result = determine_service_name(endpoint)
assert result == 'bedrock-agentcore', f'Failed for endpoint: {endpoint}'

def test_validate_service_name_service_parsing_simple_hostname(self):
"""Test parsing service from simple hostname."""
endpoint = 'https://myservice'
Expand Down