Skip to content
Closed
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
46 changes: 43 additions & 3 deletions tests/integ/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ These tests can be run against two types of Remote MCP Servers
1. Hosted in Bedrock AgentCore Runtime
1. Against a Remote URL endpoint

#### Hosted in Bedrock AgentCore Runtime
### Hosted in Bedrock AgentCore Runtime

The Simple MCP Server is ready to easily install on AgentCore Bedrock. It is recommended to follow this testing path to ensure sigv4 is working correctly.

Expand Down Expand Up @@ -47,8 +47,7 @@ Run test against the AgentCore hosted MCP Server
uv run pytest -m integ
```


#### Against a Remote URL endpoint
### Against a Remote URL endpoint

To make testing locally faster, you can also run tests against a remote URL. Since this endpoint might not be hosted on AWS, the sigv4 code path might not be fully tested.

Expand All @@ -65,3 +64,44 @@ export REMOTE_ENDPOINT_URL=http://127.0.0.1:8000/mcp
```bash
uv run pytest -m integ
```

## Manual CLI Testing Tool

For manual testing and debugging of MCP client connectivity, a dedicated CLI tool is available at `tests/integ/manual_test.py`. This tool allows you to manually test MCP server connections outside of the automated test suite.

### Purpose

The manual test tool is useful for:
- Manual testing during development
- Debugging connection issues with specific MCP endpoints
- Testing custom service configurations

### Usage

The tool supports the following command structure:

```bash
uv run python -m tests.integ.manual_test --endpoint <url> [--service <service>] [--region <region>] --list-tools
```

#### Examples

**Basic usage (service inferred from endpoint):**
```bash
uv run python -m tests.integ.manual_test --endpoint https://my-mcp-server.amazonaws.com --list-tools
```

**With explicit service and region:**
```bash
uv run python -m tests.integ.manual_test --endpoint https://my-endpoint.com --service bedrock --region us-west-2 --list-tools
```

**Service override example:**
```bash
uv run python -m tests.integ.manual_test --endpoint https://custom-endpoint.com --service lambda --list-tools
```

**With debug logging:**
```bash
uv run python -m tests.integ.manual_test --endpoint https://my-endpoint.com --log-level DEBUG --list-tools
```
143 changes: 143 additions & 0 deletions tests/integ/manual_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
#!/usr/bin/env python3
"""Manual CLI test tool for AWS MCP Proxy.

This tool allows manual testing of MCP client functionality against remote MCP servers.
"""

import argparse
import asyncio
import logging
import os
import sys
from typing import Optional


# Add the parent directory to sys.path to import the mcp module
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

from tests.integ.mcp.simple_mcp_client import build_mcp_client


logger = logging.getLogger(__name__)


def setup_logging(log_level: str = 'INFO') -> None:
"""Set up logging configuration."""
logging.basicConfig(
level=getattr(logging, log_level.upper()),
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[logging.StreamHandler()],
)


def parse_args() -> argparse.Namespace:
"""Parse command line arguments."""
parser = argparse.ArgumentParser(
description='Manual CLI test tool for AWS MCP Proxy',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Basic usage (service inferred from endpoint)
uv run tests/integ/manual_test.py --endpoint https://my-mcp-server.amazonaws.com --list-tools

# With explicit service and region
uv run tests/integ/manual_test.py --endpoint https://my-endpoint.com --service bedrock --region us-west-2 --list-tools

# Service override example
uv run tests/integ/manual_test.py --endpoint https://custom-endpoint.com --service lambda --list-tools
""",
)

parser.add_argument(
'--endpoint',
required=True,
help='MCP server endpoint URL',
)

parser.add_argument(
'--service',
help='AWS service name for SigV4 signing (inferred from endpoint if not provided)',
)

parser.add_argument(
'--region',
help='AWS region to use (uses AWS_REGION environment variable if not provided, with final fallback to us-east-1)',
default=os.getenv('AWS_REGION', 'us-east-1'),
)

parser.add_argument(
'--list-tools',
action='store_true',
help='List available tools from the MCP server',
)

parser.add_argument(
'--log-level',
choices=['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'],
default='INFO',
help='Set the logging level (default: INFO)',
)

return parser.parse_args()


async def list_tools_command(endpoint: str, region: str, service: Optional[str] = None) -> None:
"""Execute the list-tools command."""
logger.info(f'Connecting to MCP server at: {endpoint}')
logger.info(f'Using region: {region}')
if service:
logger.info(f'Using service: {service}')
else:
logger.info('Service will be inferred from endpoint')

try:
# Build MCP client
client = build_mcp_client(endpoint=endpoint, region_name=region, service=service)

# Connect and list tools
async with client:
logger.info('Connected to MCP server, listing tools...')
tools = await client.list_tools()

if not tools:
print('\nNo tools found on the MCP server.')
return

print(f'\nFound {len(tools)} tool(s) on the MCP server:')
print('=' * 50)

for i, tool in enumerate(tools, 1):
print(f'{i}. {tool.name}')
if hasattr(tool, 'description') and tool.description:
print(f' Description: {tool.description}')
if hasattr(tool, 'inputSchema') and tool.inputSchema:
print(f' Input Schema: {tool.inputSchema}')
print()

except KeyboardInterrupt:
logger.info('Operation cancelled by user')
sys.exit(1)
except Exception as e:
logger.error(f'Error connecting to MCP server: {e}')
sys.exit(1)


async def main() -> None:
"""Main entry point."""
args = parse_args()

# Set up logging
setup_logging(args.log_level)

# Validate arguments
if not args.list_tools:
logger.error('No action specified. Use --list-tools to list available tools.')
sys.exit(1)

# Execute the requested command
if args.list_tools:
await list_tools_command(endpoint=args.endpoint, region=args.region, service=args.service)


if __name__ == '__main__':
asyncio.run(main())
28 changes: 19 additions & 9 deletions tests/integ/mcp/simple_mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,22 @@
import logging
from fastmcp.client import StdioTransport
from fastmcp.client.elicitation import ElicitResult
from typing import Optional


logger = logging.getLogger(__name__)


def build_mcp_client(endpoint: str, region_name: str) -> fastmcp.Client:
def build_mcp_client(
endpoint: str, region_name: str, service: Optional[str] = None
) -> fastmcp.Client:
"""Create a MCP Client using the aws-mcp-proxy against a remote MCP Server."""
return fastmcp.Client(
StdioTransport(
**_build_mcp_config(
endpoint=endpoint,
region_name=region_name,
service=service,
)
),
elicitation_handler=_basic_elicitation_handler,
Expand All @@ -39,7 +43,7 @@ async def _basic_elicitation_handler(message: str, response_type: type, params,
raise RuntimeError(f'Unknown Response-type, rather failing - {response_type}')


def _build_mcp_config(endpoint: str, region_name: str):
def _build_mcp_config(endpoint: str, region_name: str, service: Optional[str] = None):
credentials = boto3.Session().get_credentials()

environment_variables = {
Expand All @@ -49,14 +53,20 @@ def _build_mcp_config(endpoint: str, region_name: str):
'AWS_SESSION_TOKEN': credentials.token,
}

args = [
endpoint,
'--log-level',
'DEBUG',
'--region',
region_name,
]

# Add service parameter if provided
if service:
args.extend(['--service', service])

return {
'command': 'aws-mcp-proxy',
'args': [
endpoint,
'--log-level',
'DEBUG',
'--region',
region_name,
],
'args': args,
'env': environment_variables,
}