From c93cd2176571c36361da9a8ca89e6a3f6e7cc6f0 Mon Sep 17 00:00:00 2001 From: Leonardo Araneda Freccero Date: Tue, 11 Nov 2025 15:43:59 +0100 Subject: [PATCH] fix(mcp_proxy_for_aws/utils.py): add override for bedrock-agentcore service name detection Fixes failures when connecting to an Agentcore service. --- README.md | 11 ++++++- mcp_proxy_for_aws/utils.py | 60 ++++++++++++++++++++++++++------------ tests/unit/test_utils.py | 14 +++++++++ 3 files changed, 65 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index ef8476f..52a3044 100644 --- a/README.md +++ b/README.md @@ -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 | @@ -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: diff --git a/mcp_proxy_for_aws/utils.py b/mcp_proxy_for_aws/utils.py index 7c588c0..5d00cf7 100644 --- a/mcp_proxy_for_aws/utils.py +++ b/mcp_proxy_for_aws/utils.py @@ -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 @@ -72,8 +71,38 @@ 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 @@ -81,6 +110,7 @@ def determine_service_name(endpoint: str, service: Optional[str] = None) -> str: Returns: Validated service name + Validated region Raises: ValueError: If service cannot be determined @@ -88,18 +118,14 @@ def determine_service_name(endpoint: str, service: Optional[str] = None) -> str: 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 @@ -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: diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 15e09d1..a58ef22 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -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'