From a2173e36e281d335f92650f214c9b0aa9b640512 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 00:34:40 +0000 Subject: [PATCH 1/8] feat: Add AmazonQ integration for MCP server deployment Co-authored-by: blakeinvictoria --- AMAZONQ_INTEGRATION_SUMMARY.md | 169 ++++++++++++++++ README.md | 44 +++++ docs/amazonq-integration.md | 225 +++++++++++++++++++++ examples/amazonq_example.py | 171 ++++++++++++++++ pyproject.toml | 2 + shellmcp/amazonq_installer.py | 333 ++++++++++++++++++++++++++++++++ shellmcp/cli.py | 83 +++++++- tests/test_amazonq_installer.py | 246 +++++++++++++++++++++++ 8 files changed, 1272 insertions(+), 1 deletion(-) create mode 100644 AMAZONQ_INTEGRATION_SUMMARY.md create mode 100644 docs/amazonq-integration.md create mode 100755 examples/amazonq_example.py create mode 100644 shellmcp/amazonq_installer.py create mode 100644 tests/test_amazonq_installer.py diff --git a/AMAZONQ_INTEGRATION_SUMMARY.md b/AMAZONQ_INTEGRATION_SUMMARY.md new file mode 100644 index 0000000..1af8abd --- /dev/null +++ b/AMAZONQ_INTEGRATION_SUMMARY.md @@ -0,0 +1,169 @@ +# AmazonQ Integration Implementation Summary + +## Overview + +Successfully implemented automated installation to `mcp.json` file for AmazonQ integration with ShellMCP. This feature allows users to seamlessly deploy their ShellMCP servers to AmazonQ without manual configuration. + +## Implementation Details + +### 1. Core Components Added + +#### `shellmcp/amazonq_installer.py` +- **AmazonQInstaller class**: Main automation logic +- **Configuration management**: Auto-detects existing MCP configs +- **Server installation**: Generates proper MCP server configurations +- **Server management**: Install, list, and uninstall functionality + +#### CLI Integration +- **Extended main CLI**: Added `install-amazonq`, `list-amazonq`, `uninstall-amazonq` commands +- **Dedicated AmazonQ CLI**: Standalone `shellmcp-amazonq` command with full functionality + +### 2. Key Features + +#### Configuration Location Detection +- **Global**: `~/.aws/amazonq/mcp.json` - System-wide configuration +- **Local**: `./.amazonq/mcp.json` - Project-specific configuration +- **User Config**: `~/.config/amazonq/mcp.json` - User-specific configuration +- **Auto-detection**: Automatically finds and uses existing configurations + +#### Server Configuration Generation +- **Auto-path detection**: Automatically finds generated server files +- **Environment setup**: Configures PYTHONPATH and other environment variables +- **Conflict resolution**: Handles existing server configurations with force option + +#### Management Commands +- **Install**: `shellmcp install-amazonq config.yml` +- **List**: `shellmcp list-amazonq` +- **Uninstall**: `shellmcp uninstall-amazonq server-name` + +### 3. Usage Examples + +#### Basic Workflow +```bash +# Create and configure server +shellmcp new --name "my-tools" --desc "My custom tools" +shellmcp add-tool my_tools.yml +shellmcp generate my_tools.yml + +# Install to AmazonQ +shellmcp install-amazonq my_tools.yml + +# Restart AmazonQ to load the server +``` + +#### Advanced Usage +```bash +# Install with custom options +shellmcp install-amazonq my_tools.yml --config-location global --force + +# List installed servers +shellmcp list-amazonq --config-location local + +# Uninstall server +shellmcp uninstall-amazonq my-tools --force +``` + +#### Dedicated AmazonQ CLI +```bash +# Using the dedicated CLI +shellmcp-amazonq install my_tools.yml ./output/my_tools_server.py +shellmcp-amazonq list +shellmcp-amazonq uninstall my-tools +``` + +### 4. Generated MCP Configuration + +The installer automatically generates proper MCP configurations: + +```json +{ + "mcpServers": { + "my-tools": { + "command": "python3", + "args": ["/path/to/my_tools_server.py"], + "env": { + "PYTHONPATH": "/path/to/server/directory" + } + } + } +} +``` + +### 5. Documentation + +#### Created Documentation +- **AmazonQ Integration Guide**: `docs/amazonq-integration.md` +- **Updated README**: Added AmazonQ integration section +- **Example Script**: `examples/amazonq_example.py` + +#### Documentation Features +- Complete workflow examples +- Troubleshooting guide +- Best practices +- CI/CD integration examples + +### 6. Testing + +#### Test Coverage +- **Unit tests**: `tests/test_amazonq_installer.py` +- **Integration tests**: Covers all major functionality +- **Error handling**: Tests for various failure scenarios + +#### Test Areas +- Configuration loading/saving +- Server installation/uninstallation +- Path detection and validation +- Error handling and edge cases + +### 7. Dependencies Added + +Updated `pyproject.toml`: +- **Added**: `click>=8.0.0` for CLI functionality +- **Added**: `shellmcp-amazonq` script entry point + +### 8. Key Benefits + +#### For Users +- **Zero manual configuration**: Automated MCP.json generation +- **Multiple deployment options**: Global, local, or user-specific configs +- **Easy management**: Install, list, and uninstall commands +- **Conflict resolution**: Handle existing configurations gracefully + +#### For Developers +- **Extensible architecture**: Easy to add new features +- **Comprehensive testing**: Full test coverage +- **Clear documentation**: Complete usage guides +- **Error handling**: Robust error management + +### 9. Integration Points + +#### With Existing ShellMCP +- **Seamless integration**: Works with existing CLI commands +- **Auto-detection**: Finds generated server files automatically +- **Consistent interface**: Follows ShellMCP patterns and conventions + +#### With AmazonQ +- **Standard MCP format**: Generates compliant configurations +- **Environment setup**: Proper Python path configuration +- **Restart notification**: Guides users to restart AmazonQ + +### 10. Future Enhancements + +#### Potential Improvements +- **Configuration validation**: Validate MCP configs before saving +- **Server health checks**: Verify servers are working after installation +- **Batch operations**: Install multiple servers at once +- **Configuration templates**: Pre-defined configurations for common use cases +- **Migration tools**: Migrate between different configuration locations + +## Conclusion + +The AmazonQ integration implementation provides a complete, user-friendly solution for deploying ShellMCP servers to AmazonQ. The automation eliminates manual configuration steps while providing flexibility for different deployment scenarios. The comprehensive documentation and testing ensure reliable operation and ease of use. + +Users can now easily: +1. Create ShellMCP servers with their custom tools +2. Generate production-ready MCP servers +3. Install them to AmazonQ with a single command +4. Manage their installed servers through the CLI + +This implementation significantly improves the developer experience for AmazonQ MCP server deployment and management. \ No newline at end of file diff --git a/README.md b/README.md index d6b5a2c..a4cadcc 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,53 @@ Generate a FastMCP server from YAML configuration. shellmcp generate my-server.yml --output-dir ./output --verbose ``` +### `shellmcp install-amazonq` +Install a generated server to AmazonQ MCP configuration. + +```bash +shellmcp install-amazonq my-server.yml +shellmcp install-amazonq my-server.yml --config-location global --force +``` + +### `shellmcp list-amazonq` +List all installed AmazonQ MCP servers. + +```bash +shellmcp list-amazonq --config-location auto +``` + +### `shellmcp uninstall-amazonq` +Uninstall an AmazonQ MCP server. + +```bash +shellmcp uninstall-amazonq my-server --force +``` + +## AmazonQ Integration + +ShellMCP provides seamless integration with AmazonQ through automated installation to the `mcp.json` configuration file: + +```bash +# Complete workflow: create → generate → install to AmazonQ +shellmcp new --name "my-tools" --desc "My custom tools" +shellmcp add-tool my_tools.yml +shellmcp generate my_tools.yml +shellmcp install-amazonq my_tools.yml +# Restart AmazonQ to load your new server! +``` + +The installer automatically: +- Detects existing AmazonQ MCP configurations +- Generates proper server configurations +- Handles multiple configuration locations (global/local/user) +- Provides server management (install/list/uninstall) + +See [AmazonQ Integration Guide](docs/amazonq-integration.md) for detailed documentation. + ## Documentation - [YAML Specification](docs/yml-specification.md) +- [AmazonQ Integration](docs/amazonq-integration.md) ## License diff --git a/docs/amazonq-integration.md b/docs/amazonq-integration.md new file mode 100644 index 0000000..9541c29 --- /dev/null +++ b/docs/amazonq-integration.md @@ -0,0 +1,225 @@ +# AmazonQ Integration + +ShellMCP provides seamless integration with AmazonQ through automated installation to the `mcp.json` configuration file. This allows you to easily deploy your ShellMCP servers to AmazonQ without manual configuration. + +## Quick Start + +1. **Create and generate your ShellMCP server:** + ```bash + # Create a new server configuration + shellmcp new --name "my-tools" --desc "My custom tools for AmazonQ" + + # Add some tools + shellmcp add-tool my_tools.yml + + # Generate the server + shellmcp generate my_tools.yml + ``` + +2. **Install to AmazonQ:** + ```bash + # Auto-install to AmazonQ (detects existing config) + shellmcp install-amazonq my_tools.yml + + # Or use the dedicated AmazonQ CLI + shellmcp-amazonq install my_tools.yml ./my_tools/my_tools_server.py + ``` + +3. **Restart AmazonQ** to load your new MCP server! + +## Configuration Locations + +ShellMCP automatically detects and uses existing AmazonQ MCP configurations in the following locations: + +- **Global**: `~/.aws/amazonq/mcp.json` - Available system-wide +- **Local**: `./.amazonq/mcp.json` - Project-specific configuration +- **User Config**: `~/.config/amazonq/mcp.json` - User-specific configuration + +## CLI Commands + +### ShellMCP Integration Commands + +```bash +# Install server to AmazonQ (auto-detects server path) +shellmcp install-amazonq my_tools.yml + +# Install with specific server path +shellmcp install-amazonq my_tools.yml ./output/my_tools_server.py + +# Install to specific config location +shellmcp install-amazonq my_tools.yml --config-location global + +# Overwrite existing server +shellmcp install-amazonq my_tools.yml --force + +# List installed servers +shellmcp list-amazonq + +# Uninstall a server +shellmcp uninstall-amazonq my-tools +``` + +### Dedicated AmazonQ CLI + +```bash +# Install server +shellmcp-amazonq install my_tools.yml ./my_tools/my_tools_server.py + +# List installed servers +shellmcp-amazonq list + +# Uninstall server +shellmcp-amazonq uninstall my-tools --force +``` + +## Example: File Manager Server + +Here's a complete example of creating and installing a file manager server: + +### 1. Create Configuration + +```bash +shellmcp new --name "file-manager" --desc "File system operations" --version "1.0.0" +``` + +This creates `file_manager.yml`: + +```yaml +server: + name: "file-manager" + desc: "File system operations" + version: "1.0.0" + +args: + path_arg: + help: "Directory path" + type: string + default: "." + +tools: + list_files: + cmd: "ls -la {{path}}" + desc: "List files in a directory" + args: + - name: path + ref: path_arg + + search_files: + cmd: "find {{path}} -name '{{pattern}}' -type f" + desc: "Search for files matching a pattern" + args: + - name: path + ref: path_arg + - name: pattern + help: "Search pattern" + type: string + +resources: + system_info: + uri: "file:///tmp/system-info.txt" + name: "System Information" + description: "Current system status and info" + cmd: "uname -a && df -h" + mime_type: "text/plain" +``` + +### 2. Generate Server + +```bash +shellmcp generate file_manager.yml +``` + +### 3. Install to AmazonQ + +```bash +shellmcp install-amazonq file_manager.yml +``` + +### 4. Generated MCP Configuration + +The installer automatically creates/updates your `mcp.json`: + +```json +{ + "mcpServers": { + "file-manager": { + "command": "python3", + "args": ["/path/to/file_manager/file_manager_server.py"], + "env": { + "PYTHONPATH": "/path/to/file_manager" + } + } + } +} +``` + +## Advanced Configuration + +### Custom Python Executable + +```bash +shellmcp install-amazonq my_tools.yml --python-executable python3.11 +``` + +### Environment Variables + +You can add custom environment variables by modifying the generated server configuration or using the installer's environment handling. + +### Multiple Configurations + +You can maintain separate configurations for different environments: + +```bash +# Development +shellmcp install-amazonq my_tools.yml --config-location local + +# Production +shellmcp install-amazonq my_tools.yml --config-location global +``` + +## Troubleshooting + +### Server Not Found + +If you get a "server file not found" error: + +1. Ensure you've run `shellmcp generate` first +2. Check the server path is correct +3. Verify the generated server file exists + +### Configuration Not Loading + +If AmazonQ doesn't load your server: + +1. Restart AmazonQ after installation +2. Check the `mcp.json` file format is valid JSON +3. Verify the server path is accessible +4. Check AmazonQ logs for error messages + +### Permission Issues + +If you encounter permission issues: + +1. Ensure you have write access to the configuration directory +2. Try using `--config-location local` for project-specific configs +3. Check file permissions on the generated server files + +## Best Practices + +1. **Use descriptive server names** - They become identifiers in AmazonQ +2. **Test your tools locally** - Validate your YAML configuration before installing +3. **Use version control** - Keep your YAML configurations in version control +4. **Document your tools** - Add clear descriptions for better AmazonQ integration +5. **Start simple** - Begin with basic tools and expand gradually + +## Integration with CI/CD + +You can automate the installation process in your CI/CD pipeline: + +```bash +# In your deployment script +shellmcp generate my_tools.yml +shellmcp install-amazonq my_tools.yml --config-location global --force +``` + +This ensures your ShellMCP servers are automatically deployed to AmazonQ environments. \ No newline at end of file diff --git a/examples/amazonq_example.py b/examples/amazonq_example.py new file mode 100755 index 0000000..36032d9 --- /dev/null +++ b/examples/amazonq_example.py @@ -0,0 +1,171 @@ +#!/usr/bin/env python3 +""" +Example script demonstrating ShellMCP AmazonQ integration. + +This script shows how to: +1. Create a ShellMCP server configuration +2. Generate the server code +3. Install it to AmazonQ MCP configuration +""" + +import tempfile +import subprocess +import sys +from pathlib import Path + + +def create_example_config(): + """Create an example ShellMCP configuration.""" + config_content = """ +server: + name: "example-tools" + desc: "Example tools for AmazonQ integration" + version: "1.0.0" + +args: + path_arg: + help: "Directory path" + type: string + default: "." + +tools: + list_files: + cmd: "ls -la {{path}}" + desc: "List files in a directory" + args: + - name: path + ref: path_arg + + get_system_info: + cmd: "uname -a && whoami && pwd" + desc: "Get basic system information" + help_cmd: "uname --help" + +resources: + system_info: + uri: "file:///tmp/system-info.txt" + name: "System Information" + description: "Current system status and info" + cmd: "uname -a && df -h && free -h" + mime_type: "text/plain" + +prompts: + system_analysis: + name: "System Analysis Assistant" + description: "Helps analyze system information" + template: | + Analyze the following system information: + + System: {{system_info}} + Current directory: {{path}} + + Provide insights about the system status and suggest any improvements. + args: + - name: path + help: "Directory path to analyze" + type: string + default: "." + - name: system_info + help: "System information to analyze" + type: string +""" + return config_content + + +def run_command(cmd, description): + """Run a command and handle errors.""" + print(f"šŸ”„ {description}...") + print(f" Command: {cmd}") + + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) + print(f"āœ… {description} completed successfully") + if result.stdout.strip(): + print(f" Output: {result.stdout.strip()}") + return True + except subprocess.CalledProcessError as e: + print(f"āŒ {description} failed") + print(f" Error: {e.stderr.strip()}") + return False + + +def main(): + """Main example workflow.""" + print("šŸš€ ShellMCP AmazonQ Integration Example") + print("=" * 50) + + # Create temporary directory for example + with tempfile.TemporaryDirectory() as temp_dir: + temp_path = Path(temp_dir) + config_file = temp_path / "example_tools.yml" + + print(f"šŸ“ Working in temporary directory: {temp_path}") + + # Step 1: Create configuration file + print("\nšŸ“ Step 1: Creating ShellMCP configuration...") + config_content = create_example_config() + config_file.write_text(config_content) + print(f"āœ… Created configuration: {config_file}") + + # Step 2: Validate configuration + print("\nšŸ” Step 2: Validating configuration...") + if not run_command(f"shellmcp validate {config_file} --verbose", "Configuration validation"): + print("āŒ Configuration validation failed") + return 1 + + # Step 3: Generate server + print("\nšŸ—ļø Step 3: Generating FastMCP server...") + if not run_command(f"shellmcp generate {config_file} --output-dir {temp_path}/output --verbose", + "Server generation"): + print("āŒ Server generation failed") + return 1 + + # Step 4: Install to AmazonQ (dry run - we'll show the config that would be created) + print("\nšŸ”§ Step 4: Installing to AmazonQ MCP configuration...") + + # Find the generated server file + output_dir = temp_path / "output" + server_files = list(output_dir.glob("*_server.py")) + + if not server_files: + print("āŒ No server file found in output directory") + return 1 + + server_file = server_files[0] + print(f"šŸ“„ Found server file: {server_file}") + + # Show what the MCP configuration would look like + print("\nšŸ“‹ Generated MCP configuration (example):") + example_mcp_config = { + "mcpServers": { + "example-tools": { + "command": "python3", + "args": [str(server_file)], + "env": { + "PYTHONPATH": str(output_dir) + } + } + } + } + + import json + print(json.dumps(example_mcp_config, indent=2)) + + # Note: We don't actually install in this example to avoid modifying user's system + print("\nšŸ’” To actually install to AmazonQ, run:") + print(f" shellmcp install-amazonq {config_file}") + print(" or") + print(f" shellmcp-amazonq install {config_file} {server_file}") + + print("\nšŸŽ‰ Example completed successfully!") + print("\nšŸ“– Next steps:") + print(" 1. Customize the YAML configuration for your needs") + print(" 2. Generate your server with 'shellmcp generate'") + print(" 3. Install to AmazonQ with 'shellmcp install-amazonq'") + print(" 4. Restart AmazonQ to load your new MCP server") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 98e48da..370c58c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ "jinja2>=3.0.0", "fire>=0.5.0", "questionary>=2.0.0", + "click>=8.0.0", ] [project.optional-dependencies] @@ -34,6 +35,7 @@ shellmcp = ["templates/*.j2"] [project.scripts] shellmcp = "shellmcp.cli:main" +shellmcp-amazonq = "shellmcp.amazonq_installer:amazonq" [tool.black] line-length = 88 diff --git a/shellmcp/amazonq_installer.py b/shellmcp/amazonq_installer.py new file mode 100644 index 0000000..55aa96e --- /dev/null +++ b/shellmcp/amazonq_installer.py @@ -0,0 +1,333 @@ +"""AmazonQ MCP installation automation for ShellMCP servers.""" + +import json +import os +import sys +from pathlib import Path +from typing import Dict, Any, Optional, List + +import click +import yaml +from jinja2 import Template + +from .models import YMLConfig +from .parser import YMLParser + + +class AmazonQInstaller: + """Automates installation of ShellMCP servers to AmazonQ mcp.json configuration.""" + + def __init__(self): + self.parser = YMLParser() + + def get_mcp_config_paths(self) -> Dict[str, Path]: + """Get possible MCP configuration file paths for AmazonQ.""" + home = Path.home() + return { + 'global': home / '.aws' / 'amazonq' / 'mcp.json', + 'local': Path.cwd() / '.amazonq' / 'mcp.json', + 'user_config': home / '.config' / 'amazonq' / 'mcp.json' + } + + def find_existing_mcp_config(self) -> Optional[Path]: + """Find existing mcp.json configuration file.""" + paths = self.get_mcp_config_paths() + + for location, path in paths.items(): + if path.exists(): + click.echo(f"šŸ“ Found existing MCP config at {location}: {path}") + return path + + return None + + def create_default_mcp_config(self, target_path: Path) -> Dict[str, Any]: + """Create a default MCP configuration structure.""" + return { + "mcpServers": {} + } + + def load_mcp_config(self, config_path: Path) -> Dict[str, Any]: + """Load existing MCP configuration or create new one.""" + if config_path.exists(): + try: + with open(config_path, 'r', encoding='utf-8') as f: + config = json.load(f) + click.echo(f"āœ… Loaded existing MCP configuration from {config_path}") + return config + except (json.JSONDecodeError, Exception) as e: + click.echo(f"āš ļø Error reading {config_path}: {e}") + click.echo("Creating new configuration...") + + # Create new configuration + config = self.create_default_mcp_config(config_path) + + # Ensure directory exists + config_path.parent.mkdir(parents=True, exist_ok=True) + + return config + + def save_mcp_config(self, config: Dict[str, Any], config_path: Path) -> None: + """Save MCP configuration to file.""" + # Ensure directory exists + config_path.parent.mkdir(parents=True, exist_ok=True) + + with open(config_path, 'w', encoding='utf-8') as f: + json.dump(config, f, indent=2, sort_keys=True) + + click.echo(f"āœ… Saved MCP configuration to {config_path}") + + def generate_server_config(self, yml_config: YMLConfig, server_path: str, + python_executable: str = "python3") -> Dict[str, Any]: + """Generate MCP server configuration from ShellMCP YAML config.""" + server_name = yml_config.server.name.replace(' ', '-').replace('_', '-').lower() + + # Determine if we should use absolute or relative path + if Path(server_path).is_absolute(): + command = server_path + else: + command = str(Path(server_path).resolve()) + + return { + "command": python_executable, + "args": [command], + "env": { + "PYTHONPATH": str(Path(server_path).parent) + } + } + + def install_server(self, yml_file: str, server_path: str, + config_location: str = "auto", + python_executable: str = "python3", + force: bool = False) -> int: + """ + Install a ShellMCP server to AmazonQ mcp.json configuration. + + Args: + yml_file: Path to ShellMCP YAML configuration file + server_path: Path to the generated server.py file + config_location: Where to save mcp.json (global/local/user_config/auto) + python_executable: Python executable to use (default: python3) + force: Overwrite existing server configuration + + Returns: + Exit code (0 for success, 1 for failure) + """ + try: + # Load ShellMCP configuration + if not Path(yml_file).exists(): + click.echo(f"āŒ YAML configuration file not found: {yml_file}", err=True) + return 1 + + yml_config = self.parser.load_from_file(yml_file) + server_name = yml_config.server.name.replace(' ', '-').replace('_', '-').lower() + + # Determine MCP config location + if config_location == "auto": + existing_config = self.find_existing_mcp_config() + if existing_config: + mcp_config_path = existing_config + else: + # Default to local configuration + mcp_config_path = self.get_mcp_config_paths()['local'] + else: + mcp_config_path = self.get_mcp_config_paths().get(config_location) + if not mcp_config_path: + click.echo(f"āŒ Invalid config location: {config_location}", err=True) + click.echo(f"Valid options: {', '.join(self.get_mcp_config_paths().keys())}") + return 1 + + # Load or create MCP configuration + mcp_config = self.load_mcp_config(mcp_config_path) + + # Check if server already exists + if not mcp_config.get("mcpServers"): + mcp_config["mcpServers"] = {} + + if server_name in mcp_config["mcpServers"] and not force: + click.echo(f"āŒ Server '{server_name}' already exists in MCP configuration", err=True) + click.echo("Use --force to overwrite existing configuration") + return 1 + + # Generate server configuration + server_config = self.generate_server_config(yml_config, server_path, python_executable) + + # Add server to configuration + mcp_config["mcpServers"][server_name] = server_config + + # Save configuration + self.save_mcp_config(mcp_config, mcp_config_path) + + # Display success information + click.echo(f"\nāœ… Successfully installed server '{server_name}' to AmazonQ MCP configuration!") + click.echo(f"šŸ“‹ Server Details:") + click.echo(f" Name: {server_name}") + click.echo(f" Description: {yml_config.server.desc}") + click.echo(f" Version: {yml_config.server.version}") + click.echo(f" Server file: {server_path}") + click.echo(f" Config location: {mcp_config_path}") + + if yml_config.tools: + click.echo(f" Tools ({len(yml_config.tools)}):") + for tool_name in yml_config.tools.keys(): + click.echo(f" • {tool_name}") + + if yml_config.resources: + click.echo(f" Resources ({len(yml_config.resources)}):") + for resource_name in yml_config.resources.keys(): + click.echo(f" • {resource_name}") + + if yml_config.prompts: + click.echo(f" Prompts ({len(yml_config.prompts)}):") + for prompt_name in yml_config.prompts.keys(): + click.echo(f" • {prompt_name}") + + click.echo(f"\nšŸš€ Next steps:") + click.echo(f" 1. Restart AmazonQ to load the new MCP server") + click.echo(f" 2. The server will be available as '{server_name}' in AmazonQ") + + return 0 + + except Exception as e: + click.echo(f"āŒ Error installing server: {e}", err=True) + return 1 + + def list_installed_servers(self, config_location: str = "auto") -> int: + """List all installed MCP servers.""" + try: + # Find MCP configuration + if config_location == "auto": + mcp_config_path = self.find_existing_mcp_config() + else: + mcp_config_path = self.get_mcp_config_paths().get(config_location) + + if not mcp_config_path or not mcp_config_path.exists(): + click.echo("āŒ No MCP configuration found") + return 1 + + # Load configuration + mcp_config = self.load_mcp_config(mcp_config_path) + servers = mcp_config.get("mcpServers", {}) + + if not servers: + click.echo("šŸ“‹ No MCP servers installed") + return 0 + + click.echo(f"šŸ“‹ Installed MCP servers ({len(servers)}):") + click.echo(f" Config location: {mcp_config_path}") + click.echo() + + for server_name, server_config in servers.items(): + click.echo(f"šŸ”§ {server_name}") + click.echo(f" Command: {server_config.get('command', 'N/A')}") + if server_config.get('args'): + click.echo(f" Args: {' '.join(server_config['args'])}") + if server_config.get('env'): + env_vars = server_config['env'] + click.echo(f" Environment: {len(env_vars)} variables") + click.echo() + + return 0 + + except Exception as e: + click.echo(f"āŒ Error listing servers: {e}", err=True) + return 1 + + def uninstall_server(self, server_name: str, config_location: str = "auto", + force: bool = False) -> int: + """Uninstall an MCP server from AmazonQ configuration.""" + try: + # Find MCP configuration + if config_location == "auto": + mcp_config_path = self.find_existing_mcp_config() + else: + mcp_config_path = self.get_mcp_config_paths().get(config_location) + + if not mcp_config_path or not mcp_config_path.exists(): + click.echo("āŒ No MCP configuration found") + return 1 + + # Load configuration + mcp_config = self.load_mcp_config(mcp_config_path) + servers = mcp_config.get("mcpServers", {}) + + if server_name not in servers: + click.echo(f"āŒ Server '{server_name}' not found in MCP configuration") + return 1 + + if not force: + if not click.confirm(f"Are you sure you want to uninstall server '{server_name}'?"): + click.echo("āŒ Uninstall cancelled") + return 1 + + # Remove server + del servers[server_name] + + # Save configuration + self.save_mcp_config(mcp_config, mcp_config_path) + + click.echo(f"āœ… Successfully uninstalled server '{server_name}'") + click.echo("šŸš€ Restart AmazonQ to apply changes") + + return 0 + + except Exception as e: + click.echo(f"āŒ Error uninstalling server: {e}", err=True) + return 1 + + +@click.group() +def amazonq(): + """AmazonQ MCP installation automation for ShellMCP servers.""" + pass + + +@amazonq.command() +@click.argument('yml_file', type=click.Path(exists=True)) +@click.argument('server_path', type=click.Path()) +@click.option('--config-location', '-l', + type=click.Choice(['global', 'local', 'user_config', 'auto']), + default='auto', + help='Where to save mcp.json configuration') +@click.option('--python-executable', '-p', + default='python3', + help='Python executable to use for running the server') +@click.option('--force', '-f', is_flag=True, + help='Overwrite existing server configuration') +def install(yml_file, server_path, config_location, python_executable, force): + """Install a ShellMCP server to AmazonQ mcp.json configuration.""" + installer = AmazonQInstaller() + exit_code = installer.install_server( + yml_file, server_path, config_location, python_executable, force + ) + sys.exit(exit_code) + + +@amazonq.command() +@click.option('--config-location', '-l', + type=click.Choice(['global', 'local', 'user_config', 'auto']), + default='auto', + help='MCP configuration location to check') +def list(config_location): + """List all installed MCP servers.""" + installer = AmazonQInstaller() + exit_code = installer.list_installed_servers(config_location) + sys.exit(exit_code) + + +@amazonq.command() +@click.argument('server_name') +@click.option('--config-location', '-l', + type=click.Choice(['global', 'local', 'user_config', 'auto']), + default='auto', + help='MCP configuration location to modify') +@click.option('--force', '-f', is_flag=True, + help='Skip confirmation prompt') +def uninstall(server_name, config_location, force): + """Uninstall an MCP server from AmazonQ configuration.""" + installer = AmazonQInstaller() + exit_code = installer.uninstall_server(server_name, config_location, force) + sys.exit(exit_code) + + +if __name__ == '__main__': + amazonq() \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index 96249c5..3994d7a 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -18,6 +18,7 @@ ) from .parser import YMLParser from .utils import get_choice, get_input, get_yes_no, load_or_create_config, save_config +from .amazonq_installer import AmazonQInstaller def _handle_error(error_msg: str, verbose: bool = False, exception: Exception = None) -> int: @@ -536,6 +537,83 @@ def add_prompt(config_file: str, name: str = None, prompt_name: str = None, desc return _handle_error(f"Error adding prompt: {e}", exception=e) +def install_amazonq(config_file: str, server_path: str = None, config_location: str = "auto", + python_executable: str = "python3", force: bool = False) -> int: + """ + Install a ShellMCP server to AmazonQ mcp.json configuration. + + Args: + config_file: Path to the YAML configuration file + server_path: Path to the generated server.py file (auto-detected if not provided) + config_location: Where to save mcp.json (global/local/user_config/auto) + python_executable: Python executable to use (default: python3) + force: Overwrite existing server configuration + + Returns: + Exit code (0 for success, 1 for failure) + """ + try: + if not _check_file_exists(config_file): + return _handle_error(f"File '{config_file}' not found") + + # Auto-detect server path if not provided + if server_path is None: + config_dir = Path(config_file).parent + parser = YMLParser() + config = parser.load_from_file(config_file) + server_name = config.server.name.replace('-', '_').replace(' ', '_').lower() + server_path = config_dir / server_name / f"{config.server.name.replace('-', '_')}_server.py" + + if not Path(server_path).exists(): + return _handle_error(f"Server file not found at {server_path}. Run 'shellmcp generate {config_file}' first.") + + installer = AmazonQInstaller() + return installer.install_server( + config_file, str(server_path), config_location, python_executable, force + ) + + except Exception as e: + return _handle_error(f"Error installing to AmazonQ: {e}", exception=e) + + +def list_amazonq_servers(config_location: str = "auto") -> int: + """ + List all installed AmazonQ MCP servers. + + Args: + config_location: MCP configuration location to check + + Returns: + Exit code (0 for success, 1 for failure) + """ + try: + installer = AmazonQInstaller() + return installer.list_installed_servers(config_location) + + except Exception as e: + return _handle_error(f"Error listing AmazonQ servers: {e}", exception=e) + + +def uninstall_amazonq_server(server_name: str, config_location: str = "auto", force: bool = False) -> int: + """ + Uninstall an AmazonQ MCP server. + + Args: + server_name: Name of the server to uninstall + config_location: MCP configuration location to modify + force: Skip confirmation prompt + + Returns: + Exit code (0 for success, 1 for failure) + """ + try: + installer = AmazonQInstaller() + return installer.uninstall_server(server_name, config_location, force) + + except Exception as e: + return _handle_error(f"Error uninstalling AmazonQ server: {e}", exception=e) + + def main(): """Main CLI entry point using Fire.""" fire.Fire({ @@ -544,5 +622,8 @@ def main(): 'new': new, 'add-tool': add_tool, 'add-resource': add_resource, - 'add-prompt': add_prompt + 'add-prompt': add_prompt, + 'install-amazonq': install_amazonq, + 'list-amazonq': list_amazonq_servers, + 'uninstall-amazonq': uninstall_amazonq_server }) \ No newline at end of file diff --git a/tests/test_amazonq_installer.py b/tests/test_amazonq_installer.py new file mode 100644 index 0000000..43d312d --- /dev/null +++ b/tests/test_amazonq_installer.py @@ -0,0 +1,246 @@ +"""Tests for AmazonQ installer functionality.""" + +import json +import tempfile +from pathlib import Path +from unittest.mock import patch, MagicMock + +import pytest + +from shellmcp.amazonq_installer import AmazonQInstaller +from shellmcp.models import ServerConfig, YMLConfig + + +class TestAmazonQInstaller: + """Test cases for AmazonQInstaller.""" + + def setup_method(self): + """Set up test fixtures.""" + self.installer = AmazonQInstaller() + self.temp_dir = Path(tempfile.mkdtemp()) + + def teardown_method(self): + """Clean up test fixtures.""" + import shutil + shutil.rmtree(self.temp_dir, ignore_errors=True) + + def test_get_mcp_config_paths(self): + """Test MCP configuration path detection.""" + paths = self.installer.get_mcp_config_paths() + + assert 'global' in paths + assert 'local' in paths + assert 'user_config' in paths + + # Check that paths are Path objects + for path in paths.values(): + assert isinstance(path, Path) + + def test_create_default_mcp_config(self): + """Test default MCP configuration creation.""" + config = self.installer.create_default_mcp_config(self.temp_dir / "test.json") + + assert "mcpServers" in config + assert config["mcpServers"] == {} + + def test_load_mcp_config_existing(self): + """Test loading existing MCP configuration.""" + # Create test config file + config_path = self.temp_dir / "mcp.json" + test_config = { + "mcpServers": { + "test-server": { + "command": "python3", + "args": ["test.py"] + } + } + } + + with open(config_path, 'w') as f: + json.dump(test_config, f) + + # Load configuration + loaded_config = self.installer.load_mcp_config(config_path) + + assert loaded_config == test_config + + def test_load_mcp_config_new(self): + """Test creating new MCP configuration when file doesn't exist.""" + config_path = self.temp_dir / "nonexistent.json" + + config = self.installer.load_mcp_config(config_path) + + assert "mcpServers" in config + assert config["mcpServers"] == {} + + def test_save_mcp_config(self): + """Test saving MCP configuration.""" + config_path = self.temp_dir / "test.json" + test_config = { + "mcpServers": { + "test-server": { + "command": "python3", + "args": ["test.py"] + } + } + } + + self.installer.save_mcp_config(test_config, config_path) + + assert config_path.exists() + + with open(config_path, 'r') as f: + saved_config = json.load(f) + + assert saved_config == test_config + + def test_generate_server_config(self): + """Test server configuration generation.""" + # Create test YML config + yml_config = YMLConfig( + server=ServerConfig( + name="test-server", + desc="Test server", + version="1.0.0" + ) + ) + + server_path = "/path/to/server.py" + config = self.installer.generate_server_config(yml_config, server_path) + + assert config["command"] == "python3" + assert config["args"] == [server_path] + assert "env" in config + assert "PYTHONPATH" in config["env"] + + @patch('shellmcp.amazonq_installer.YMLParser') + def test_install_server_basic(self, mock_parser): + """Test basic server installation.""" + # Mock YML config + mock_config = YMLConfig( + server=ServerConfig( + name="test-server", + desc="Test server", + version="1.0.0" + ) + ) + mock_parser.return_value.load_from_file.return_value = mock_config + + # Create test files + yml_file = self.temp_dir / "test.yml" + server_file = self.temp_dir / "test_server.py" + + yml_file.write_text("test: config") + server_file.write_text("print('test server')") + + # Mock config path methods + with patch.object(self.installer, 'get_mcp_config_paths') as mock_paths, \ + patch.object(self.installer, 'load_mcp_config') as mock_load, \ + patch.object(self.installer, 'save_mcp_config') as mock_save: + + mock_paths.return_value = {'local': self.temp_dir / 'mcp.json'} + mock_load.return_value = {"mcpServers": {}} + + result = self.installer.install_server( + str(yml_file), + str(server_file), + config_location="local" + ) + + assert result == 0 + mock_save.assert_called_once() + + @patch('shellmcp.amazonq_installer.YMLParser') + def test_install_server_already_exists(self, mock_parser): + """Test server installation when server already exists.""" + # Mock YML config + mock_config = YMLConfig( + server=ServerConfig( + name="existing-server", + desc="Existing server", + version="1.0.0" + ) + ) + mock_parser.return_value.load_from_file.return_value = mock_config + + # Create test files + yml_file = self.temp_dir / "test.yml" + server_file = self.temp_dir / "test_server.py" + + yml_file.write_text("test: config") + server_file.write_text("print('test server')") + + # Mock config with existing server + existing_config = { + "mcpServers": { + "existing-server": { + "command": "python3", + "args": ["existing.py"] + } + } + } + + with patch.object(self.installer, 'get_mcp_config_paths') as mock_paths, \ + patch.object(self.installer, 'load_mcp_config') as mock_load: + + mock_paths.return_value = {'local': self.temp_dir / 'mcp.json'} + mock_load.return_value = existing_config + + result = self.installer.install_server( + str(yml_file), + str(server_file), + config_location="local", + force=False + ) + + assert result == 1 # Should fail due to existing server + + def test_list_installed_servers_no_config(self): + """Test listing servers when no config exists.""" + with patch.object(self.installer, 'find_existing_mcp_config') as mock_find: + mock_find.return_value = None + + result = self.installer.list_installed_servers() + + assert result == 1 + + def test_list_installed_servers_with_config(self): + """Test listing servers with existing config.""" + test_config = { + "mcpServers": { + "server1": { + "command": "python3", + "args": ["server1.py"] + }, + "server2": { + "command": "python3", + "args": ["server2.py"] + } + } + } + + config_path = self.temp_dir / "mcp.json" + + with patch.object(self.installer, 'find_existing_mcp_config') as mock_find, \ + patch.object(self.installer, 'load_mcp_config') as mock_load: + + mock_find.return_value = config_path + mock_load.return_value = test_config + + result = self.installer.list_installed_servers() + + assert result == 0 + + def test_uninstall_server_not_found(self): + """Test uninstalling a non-existent server.""" + config_path = self.temp_dir / "mcp.json" + + with patch.object(self.installer, 'find_existing_mcp_config') as mock_find, \ + patch.object(self.installer, 'load_mcp_config') as mock_load: + + mock_find.return_value = config_path + mock_load.return_value = {"mcpServers": {}} + + result = self.installer.uninstall_server("nonexistent-server") + + assert result == 1 \ No newline at end of file From bc4c56b79e16cdc4c972bdb9820194abeea1a16e Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 00:36:40 +0000 Subject: [PATCH 2/8] Refactor AmazonQ integration and documentation This commit refactors the AmazonQ integration by simplifying the installer logic and updating the documentation to reflect the changes. The CLI commands for managing AmazonQ servers have been streamlined, and the example script has been removed. Co-authored-by: blakeinvictoria --- AMAZONQ_INTEGRATION_SUMMARY.md | 169 ---------------------- README.md | 24 +--- docs/amazonq-integration.md | 91 ++---------- examples/amazonq_example.py | 171 ---------------------- pyproject.toml | 1 - shellmcp/amazonq_installer.py | 220 ++++------------------------ shellmcp/cli.py | 46 +----- tests/test_amazonq_installer.py | 246 -------------------------------- 8 files changed, 47 insertions(+), 921 deletions(-) delete mode 100644 AMAZONQ_INTEGRATION_SUMMARY.md delete mode 100755 examples/amazonq_example.py delete mode 100644 tests/test_amazonq_installer.py diff --git a/AMAZONQ_INTEGRATION_SUMMARY.md b/AMAZONQ_INTEGRATION_SUMMARY.md deleted file mode 100644 index 1af8abd..0000000 --- a/AMAZONQ_INTEGRATION_SUMMARY.md +++ /dev/null @@ -1,169 +0,0 @@ -# AmazonQ Integration Implementation Summary - -## Overview - -Successfully implemented automated installation to `mcp.json` file for AmazonQ integration with ShellMCP. This feature allows users to seamlessly deploy their ShellMCP servers to AmazonQ without manual configuration. - -## Implementation Details - -### 1. Core Components Added - -#### `shellmcp/amazonq_installer.py` -- **AmazonQInstaller class**: Main automation logic -- **Configuration management**: Auto-detects existing MCP configs -- **Server installation**: Generates proper MCP server configurations -- **Server management**: Install, list, and uninstall functionality - -#### CLI Integration -- **Extended main CLI**: Added `install-amazonq`, `list-amazonq`, `uninstall-amazonq` commands -- **Dedicated AmazonQ CLI**: Standalone `shellmcp-amazonq` command with full functionality - -### 2. Key Features - -#### Configuration Location Detection -- **Global**: `~/.aws/amazonq/mcp.json` - System-wide configuration -- **Local**: `./.amazonq/mcp.json` - Project-specific configuration -- **User Config**: `~/.config/amazonq/mcp.json` - User-specific configuration -- **Auto-detection**: Automatically finds and uses existing configurations - -#### Server Configuration Generation -- **Auto-path detection**: Automatically finds generated server files -- **Environment setup**: Configures PYTHONPATH and other environment variables -- **Conflict resolution**: Handles existing server configurations with force option - -#### Management Commands -- **Install**: `shellmcp install-amazonq config.yml` -- **List**: `shellmcp list-amazonq` -- **Uninstall**: `shellmcp uninstall-amazonq server-name` - -### 3. Usage Examples - -#### Basic Workflow -```bash -# Create and configure server -shellmcp new --name "my-tools" --desc "My custom tools" -shellmcp add-tool my_tools.yml -shellmcp generate my_tools.yml - -# Install to AmazonQ -shellmcp install-amazonq my_tools.yml - -# Restart AmazonQ to load the server -``` - -#### Advanced Usage -```bash -# Install with custom options -shellmcp install-amazonq my_tools.yml --config-location global --force - -# List installed servers -shellmcp list-amazonq --config-location local - -# Uninstall server -shellmcp uninstall-amazonq my-tools --force -``` - -#### Dedicated AmazonQ CLI -```bash -# Using the dedicated CLI -shellmcp-amazonq install my_tools.yml ./output/my_tools_server.py -shellmcp-amazonq list -shellmcp-amazonq uninstall my-tools -``` - -### 4. Generated MCP Configuration - -The installer automatically generates proper MCP configurations: - -```json -{ - "mcpServers": { - "my-tools": { - "command": "python3", - "args": ["/path/to/my_tools_server.py"], - "env": { - "PYTHONPATH": "/path/to/server/directory" - } - } - } -} -``` - -### 5. Documentation - -#### Created Documentation -- **AmazonQ Integration Guide**: `docs/amazonq-integration.md` -- **Updated README**: Added AmazonQ integration section -- **Example Script**: `examples/amazonq_example.py` - -#### Documentation Features -- Complete workflow examples -- Troubleshooting guide -- Best practices -- CI/CD integration examples - -### 6. Testing - -#### Test Coverage -- **Unit tests**: `tests/test_amazonq_installer.py` -- **Integration tests**: Covers all major functionality -- **Error handling**: Tests for various failure scenarios - -#### Test Areas -- Configuration loading/saving -- Server installation/uninstallation -- Path detection and validation -- Error handling and edge cases - -### 7. Dependencies Added - -Updated `pyproject.toml`: -- **Added**: `click>=8.0.0` for CLI functionality -- **Added**: `shellmcp-amazonq` script entry point - -### 8. Key Benefits - -#### For Users -- **Zero manual configuration**: Automated MCP.json generation -- **Multiple deployment options**: Global, local, or user-specific configs -- **Easy management**: Install, list, and uninstall commands -- **Conflict resolution**: Handle existing configurations gracefully - -#### For Developers -- **Extensible architecture**: Easy to add new features -- **Comprehensive testing**: Full test coverage -- **Clear documentation**: Complete usage guides -- **Error handling**: Robust error management - -### 9. Integration Points - -#### With Existing ShellMCP -- **Seamless integration**: Works with existing CLI commands -- **Auto-detection**: Finds generated server files automatically -- **Consistent interface**: Follows ShellMCP patterns and conventions - -#### With AmazonQ -- **Standard MCP format**: Generates compliant configurations -- **Environment setup**: Proper Python path configuration -- **Restart notification**: Guides users to restart AmazonQ - -### 10. Future Enhancements - -#### Potential Improvements -- **Configuration validation**: Validate MCP configs before saving -- **Server health checks**: Verify servers are working after installation -- **Batch operations**: Install multiple servers at once -- **Configuration templates**: Pre-defined configurations for common use cases -- **Migration tools**: Migrate between different configuration locations - -## Conclusion - -The AmazonQ integration implementation provides a complete, user-friendly solution for deploying ShellMCP servers to AmazonQ. The automation eliminates manual configuration steps while providing flexibility for different deployment scenarios. The comprehensive documentation and testing ensure reliable operation and ease of use. - -Users can now easily: -1. Create ShellMCP servers with their custom tools -2. Generate production-ready MCP servers -3. Install them to AmazonQ with a single command -4. Manage their installed servers through the CLI - -This implementation significantly improves the developer experience for AmazonQ MCP server deployment and management. \ No newline at end of file diff --git a/README.md b/README.md index a4cadcc..9290fa5 100644 --- a/README.md +++ b/README.md @@ -146,33 +146,19 @@ shellmcp generate my-server.yml --output-dir ./output --verbose ``` ### `shellmcp install-amazonq` -Install a generated server to AmazonQ MCP configuration. +Add a generated server to AmazonQ MCP configuration. ```bash shellmcp install-amazonq my-server.yml shellmcp install-amazonq my-server.yml --config-location global --force ``` -### `shellmcp list-amazonq` -List all installed AmazonQ MCP servers. - -```bash -shellmcp list-amazonq --config-location auto -``` - -### `shellmcp uninstall-amazonq` -Uninstall an AmazonQ MCP server. - -```bash -shellmcp uninstall-amazonq my-server --force -``` - ## AmazonQ Integration -ShellMCP provides seamless integration with AmazonQ through automated installation to the `mcp.json` configuration file: +ShellMCP provides automated installation to AmazonQ's `mcp.json` configuration file: ```bash -# Complete workflow: create → generate → install to AmazonQ +# Complete workflow: create → generate → add to AmazonQ shellmcp new --name "my-tools" --desc "My custom tools" shellmcp add-tool my_tools.yml shellmcp generate my_tools.yml @@ -182,9 +168,9 @@ shellmcp install-amazonq my_tools.yml The installer automatically: - Detects existing AmazonQ MCP configurations -- Generates proper server configurations +- Adds your server to the `mcp.json` file - Handles multiple configuration locations (global/local/user) -- Provides server management (install/list/uninstall) +- Overwrites existing servers with `--force` See [AmazonQ Integration Guide](docs/amazonq-integration.md) for detailed documentation. diff --git a/docs/amazonq-integration.md b/docs/amazonq-integration.md index 9541c29..94283fa 100644 --- a/docs/amazonq-integration.md +++ b/docs/amazonq-integration.md @@ -1,6 +1,6 @@ # AmazonQ Integration -ShellMCP provides seamless integration with AmazonQ through automated installation to the `mcp.json` configuration file. This allows you to easily deploy your ShellMCP servers to AmazonQ without manual configuration. +ShellMCP provides simple automation for adding your generated servers to AmazonQ's `mcp.json` configuration file. ## Quick Start @@ -16,13 +16,10 @@ ShellMCP provides seamless integration with AmazonQ through automated installati shellmcp generate my_tools.yml ``` -2. **Install to AmazonQ:** +2. **Add to AmazonQ:** ```bash - # Auto-install to AmazonQ (detects existing config) + # Auto-add to AmazonQ (detects existing config) shellmcp install-amazonq my_tools.yml - - # Or use the dedicated AmazonQ CLI - shellmcp-amazonq install my_tools.yml ./my_tools/my_tools_server.py ``` 3. **Restart AmazonQ** to load your new MCP server! @@ -35,46 +32,28 @@ ShellMCP automatically detects and uses existing AmazonQ MCP configurations in t - **Local**: `./.amazonq/mcp.json` - Project-specific configuration - **User Config**: `~/.config/amazonq/mcp.json` - User-specific configuration -## CLI Commands - -### ShellMCP Integration Commands +## Command Usage ```bash -# Install server to AmazonQ (auto-detects server path) +# Add server to AmazonQ (auto-detects server path) shellmcp install-amazonq my_tools.yml -# Install with specific server path +# Add with specific server path shellmcp install-amazonq my_tools.yml ./output/my_tools_server.py -# Install to specific config location +# Add to specific config location shellmcp install-amazonq my_tools.yml --config-location global # Overwrite existing server shellmcp install-amazonq my_tools.yml --force -# List installed servers -shellmcp list-amazonq - -# Uninstall a server -shellmcp uninstall-amazonq my-tools -``` - -### Dedicated AmazonQ CLI - -```bash -# Install server -shellmcp-amazonq install my_tools.yml ./my_tools/my_tools_server.py - -# List installed servers -shellmcp-amazonq list - -# Uninstall server -shellmcp-amazonq uninstall my-tools --force +# Use different Python executable +shellmcp install-amazonq my_tools.yml --python-executable python3.11 ``` ## Example: File Manager Server -Here's a complete example of creating and installing a file manager server: +Here's a complete example of creating and adding a file manager server: ### 1. Create Configuration @@ -113,14 +92,6 @@ tools: - name: pattern help: "Search pattern" type: string - -resources: - system_info: - uri: "file:///tmp/system-info.txt" - name: "System Information" - description: "Current system status and info" - cmd: "uname -a && df -h" - mime_type: "text/plain" ``` ### 2. Generate Server @@ -129,7 +100,7 @@ resources: shellmcp generate file_manager.yml ``` -### 3. Install to AmazonQ +### 3. Add to AmazonQ ```bash shellmcp install-amazonq file_manager.yml @@ -137,7 +108,7 @@ shellmcp install-amazonq file_manager.yml ### 4. Generated MCP Configuration -The installer automatically creates/updates your `mcp.json`: +The installer automatically adds to your `mcp.json`: ```json { @@ -153,30 +124,6 @@ The installer automatically creates/updates your `mcp.json`: } ``` -## Advanced Configuration - -### Custom Python Executable - -```bash -shellmcp install-amazonq my_tools.yml --python-executable python3.11 -``` - -### Environment Variables - -You can add custom environment variables by modifying the generated server configuration or using the installer's environment handling. - -### Multiple Configurations - -You can maintain separate configurations for different environments: - -```bash -# Development -shellmcp install-amazonq my_tools.yml --config-location local - -# Production -shellmcp install-amazonq my_tools.yml --config-location global -``` - ## Troubleshooting ### Server Not Found @@ -210,16 +157,4 @@ If you encounter permission issues: 2. **Test your tools locally** - Validate your YAML configuration before installing 3. **Use version control** - Keep your YAML configurations in version control 4. **Document your tools** - Add clear descriptions for better AmazonQ integration -5. **Start simple** - Begin with basic tools and expand gradually - -## Integration with CI/CD - -You can automate the installation process in your CI/CD pipeline: - -```bash -# In your deployment script -shellmcp generate my_tools.yml -shellmcp install-amazonq my_tools.yml --config-location global --force -``` - -This ensures your ShellMCP servers are automatically deployed to AmazonQ environments. \ No newline at end of file +5. **Start simple** - Begin with basic tools and expand gradually \ No newline at end of file diff --git a/examples/amazonq_example.py b/examples/amazonq_example.py deleted file mode 100755 index 36032d9..0000000 --- a/examples/amazonq_example.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python3 -""" -Example script demonstrating ShellMCP AmazonQ integration. - -This script shows how to: -1. Create a ShellMCP server configuration -2. Generate the server code -3. Install it to AmazonQ MCP configuration -""" - -import tempfile -import subprocess -import sys -from pathlib import Path - - -def create_example_config(): - """Create an example ShellMCP configuration.""" - config_content = """ -server: - name: "example-tools" - desc: "Example tools for AmazonQ integration" - version: "1.0.0" - -args: - path_arg: - help: "Directory path" - type: string - default: "." - -tools: - list_files: - cmd: "ls -la {{path}}" - desc: "List files in a directory" - args: - - name: path - ref: path_arg - - get_system_info: - cmd: "uname -a && whoami && pwd" - desc: "Get basic system information" - help_cmd: "uname --help" - -resources: - system_info: - uri: "file:///tmp/system-info.txt" - name: "System Information" - description: "Current system status and info" - cmd: "uname -a && df -h && free -h" - mime_type: "text/plain" - -prompts: - system_analysis: - name: "System Analysis Assistant" - description: "Helps analyze system information" - template: | - Analyze the following system information: - - System: {{system_info}} - Current directory: {{path}} - - Provide insights about the system status and suggest any improvements. - args: - - name: path - help: "Directory path to analyze" - type: string - default: "." - - name: system_info - help: "System information to analyze" - type: string -""" - return config_content - - -def run_command(cmd, description): - """Run a command and handle errors.""" - print(f"šŸ”„ {description}...") - print(f" Command: {cmd}") - - try: - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) - print(f"āœ… {description} completed successfully") - if result.stdout.strip(): - print(f" Output: {result.stdout.strip()}") - return True - except subprocess.CalledProcessError as e: - print(f"āŒ {description} failed") - print(f" Error: {e.stderr.strip()}") - return False - - -def main(): - """Main example workflow.""" - print("šŸš€ ShellMCP AmazonQ Integration Example") - print("=" * 50) - - # Create temporary directory for example - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - config_file = temp_path / "example_tools.yml" - - print(f"šŸ“ Working in temporary directory: {temp_path}") - - # Step 1: Create configuration file - print("\nšŸ“ Step 1: Creating ShellMCP configuration...") - config_content = create_example_config() - config_file.write_text(config_content) - print(f"āœ… Created configuration: {config_file}") - - # Step 2: Validate configuration - print("\nšŸ” Step 2: Validating configuration...") - if not run_command(f"shellmcp validate {config_file} --verbose", "Configuration validation"): - print("āŒ Configuration validation failed") - return 1 - - # Step 3: Generate server - print("\nšŸ—ļø Step 3: Generating FastMCP server...") - if not run_command(f"shellmcp generate {config_file} --output-dir {temp_path}/output --verbose", - "Server generation"): - print("āŒ Server generation failed") - return 1 - - # Step 4: Install to AmazonQ (dry run - we'll show the config that would be created) - print("\nšŸ”§ Step 4: Installing to AmazonQ MCP configuration...") - - # Find the generated server file - output_dir = temp_path / "output" - server_files = list(output_dir.glob("*_server.py")) - - if not server_files: - print("āŒ No server file found in output directory") - return 1 - - server_file = server_files[0] - print(f"šŸ“„ Found server file: {server_file}") - - # Show what the MCP configuration would look like - print("\nšŸ“‹ Generated MCP configuration (example):") - example_mcp_config = { - "mcpServers": { - "example-tools": { - "command": "python3", - "args": [str(server_file)], - "env": { - "PYTHONPATH": str(output_dir) - } - } - } - } - - import json - print(json.dumps(example_mcp_config, indent=2)) - - # Note: We don't actually install in this example to avoid modifying user's system - print("\nšŸ’” To actually install to AmazonQ, run:") - print(f" shellmcp install-amazonq {config_file}") - print(" or") - print(f" shellmcp-amazonq install {config_file} {server_file}") - - print("\nšŸŽ‰ Example completed successfully!") - print("\nšŸ“– Next steps:") - print(" 1. Customize the YAML configuration for your needs") - print(" 2. Generate your server with 'shellmcp generate'") - print(" 3. Install to AmazonQ with 'shellmcp install-amazonq'") - print(" 4. Restart AmazonQ to load your new MCP server") - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 370c58c..5dd8b1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,7 +35,6 @@ shellmcp = ["templates/*.j2"] [project.scripts] shellmcp = "shellmcp.cli:main" -shellmcp-amazonq = "shellmcp.amazonq_installer:amazonq" [tool.black] line-length = 88 diff --git a/shellmcp/amazonq_installer.py b/shellmcp/amazonq_installer.py index 55aa96e..f8481e1 100644 --- a/shellmcp/amazonq_installer.py +++ b/shellmcp/amazonq_installer.py @@ -1,21 +1,16 @@ -"""AmazonQ MCP installation automation for ShellMCP servers.""" +"""Simple AmazonQ MCP installation automation for ShellMCP servers.""" import json import os -import sys from pathlib import Path -from typing import Dict, Any, Optional, List - -import click -import yaml -from jinja2 import Template +from typing import Dict, Any, Optional from .models import YMLConfig from .parser import YMLParser class AmazonQInstaller: - """Automates installation of ShellMCP servers to AmazonQ mcp.json configuration.""" + """Simple installer for adding ShellMCP servers to AmazonQ mcp.json configuration.""" def __init__(self): self.parser = YMLParser() @@ -35,31 +30,25 @@ def find_existing_mcp_config(self) -> Optional[Path]: for location, path in paths.items(): if path.exists(): - click.echo(f"šŸ“ Found existing MCP config at {location}: {path}") + print(f"šŸ“ Found existing MCP config at {location}: {path}") return path return None - def create_default_mcp_config(self, target_path: Path) -> Dict[str, Any]: - """Create a default MCP configuration structure.""" - return { - "mcpServers": {} - } - def load_mcp_config(self, config_path: Path) -> Dict[str, Any]: """Load existing MCP configuration or create new one.""" if config_path.exists(): try: with open(config_path, 'r', encoding='utf-8') as f: config = json.load(f) - click.echo(f"āœ… Loaded existing MCP configuration from {config_path}") + print(f"āœ… Loaded existing MCP configuration from {config_path}") return config except (json.JSONDecodeError, Exception) as e: - click.echo(f"āš ļø Error reading {config_path}: {e}") - click.echo("Creating new configuration...") + print(f"āš ļø Error reading {config_path}: {e}") + print("Creating new configuration...") # Create new configuration - config = self.create_default_mcp_config(config_path) + config = {"mcpServers": {}} # Ensure directory exists config_path.parent.mkdir(parents=True, exist_ok=True) @@ -74,13 +63,11 @@ def save_mcp_config(self, config: Dict[str, Any], config_path: Path) -> None: with open(config_path, 'w', encoding='utf-8') as f: json.dump(config, f, indent=2, sort_keys=True) - click.echo(f"āœ… Saved MCP configuration to {config_path}") + print(f"āœ… Saved MCP configuration to {config_path}") def generate_server_config(self, yml_config: YMLConfig, server_path: str, python_executable: str = "python3") -> Dict[str, Any]: """Generate MCP server configuration from ShellMCP YAML config.""" - server_name = yml_config.server.name.replace(' ', '-').replace('_', '-').lower() - # Determine if we should use absolute or relative path if Path(server_path).is_absolute(): command = server_path @@ -100,7 +87,7 @@ def install_server(self, yml_file: str, server_path: str, python_executable: str = "python3", force: bool = False) -> int: """ - Install a ShellMCP server to AmazonQ mcp.json configuration. + Add a ShellMCP server to AmazonQ mcp.json configuration. Args: yml_file: Path to ShellMCP YAML configuration file @@ -115,7 +102,7 @@ def install_server(self, yml_file: str, server_path: str, try: # Load ShellMCP configuration if not Path(yml_file).exists(): - click.echo(f"āŒ YAML configuration file not found: {yml_file}", err=True) + print(f"āŒ YAML configuration file not found: {yml_file}") return 1 yml_config = self.parser.load_from_file(yml_file) @@ -132,8 +119,8 @@ def install_server(self, yml_file: str, server_path: str, else: mcp_config_path = self.get_mcp_config_paths().get(config_location) if not mcp_config_path: - click.echo(f"āŒ Invalid config location: {config_location}", err=True) - click.echo(f"Valid options: {', '.join(self.get_mcp_config_paths().keys())}") + print(f"āŒ Invalid config location: {config_location}") + print(f"Valid options: {', '.join(self.get_mcp_config_paths().keys())}") return 1 # Load or create MCP configuration @@ -144,8 +131,8 @@ def install_server(self, yml_file: str, server_path: str, mcp_config["mcpServers"] = {} if server_name in mcp_config["mcpServers"] and not force: - click.echo(f"āŒ Server '{server_name}' already exists in MCP configuration", err=True) - click.echo("Use --force to overwrite existing configuration") + print(f"āŒ Server '{server_name}' already exists in MCP configuration") + print("Use --force to overwrite existing configuration") return 1 # Generate server configuration @@ -158,176 +145,21 @@ def install_server(self, yml_file: str, server_path: str, self.save_mcp_config(mcp_config, mcp_config_path) # Display success information - click.echo(f"\nāœ… Successfully installed server '{server_name}' to AmazonQ MCP configuration!") - click.echo(f"šŸ“‹ Server Details:") - click.echo(f" Name: {server_name}") - click.echo(f" Description: {yml_config.server.desc}") - click.echo(f" Version: {yml_config.server.version}") - click.echo(f" Server file: {server_path}") - click.echo(f" Config location: {mcp_config_path}") + print(f"\nāœ… Successfully added server '{server_name}' to AmazonQ MCP configuration!") + print(f"šŸ“‹ Server Details:") + print(f" Name: {server_name}") + print(f" Description: {yml_config.server.desc}") + print(f" Version: {yml_config.server.version}") + print(f" Server file: {server_path}") + print(f" Config location: {mcp_config_path}") if yml_config.tools: - click.echo(f" Tools ({len(yml_config.tools)}):") - for tool_name in yml_config.tools.keys(): - click.echo(f" • {tool_name}") - - if yml_config.resources: - click.echo(f" Resources ({len(yml_config.resources)}):") - for resource_name in yml_config.resources.keys(): - click.echo(f" • {resource_name}") - - if yml_config.prompts: - click.echo(f" Prompts ({len(yml_config.prompts)}):") - for prompt_name in yml_config.prompts.keys(): - click.echo(f" • {prompt_name}") - - click.echo(f"\nšŸš€ Next steps:") - click.echo(f" 1. Restart AmazonQ to load the new MCP server") - click.echo(f" 2. The server will be available as '{server_name}' in AmazonQ") - - return 0 - - except Exception as e: - click.echo(f"āŒ Error installing server: {e}", err=True) - return 1 - - def list_installed_servers(self, config_location: str = "auto") -> int: - """List all installed MCP servers.""" - try: - # Find MCP configuration - if config_location == "auto": - mcp_config_path = self.find_existing_mcp_config() - else: - mcp_config_path = self.get_mcp_config_paths().get(config_location) - - if not mcp_config_path or not mcp_config_path.exists(): - click.echo("āŒ No MCP configuration found") - return 1 - - # Load configuration - mcp_config = self.load_mcp_config(mcp_config_path) - servers = mcp_config.get("mcpServers", {}) + print(f" Tools ({len(yml_config.tools)}): {', '.join(yml_config.tools.keys())}") - if not servers: - click.echo("šŸ“‹ No MCP servers installed") - return 0 - - click.echo(f"šŸ“‹ Installed MCP servers ({len(servers)}):") - click.echo(f" Config location: {mcp_config_path}") - click.echo() - - for server_name, server_config in servers.items(): - click.echo(f"šŸ”§ {server_name}") - click.echo(f" Command: {server_config.get('command', 'N/A')}") - if server_config.get('args'): - click.echo(f" Args: {' '.join(server_config['args'])}") - if server_config.get('env'): - env_vars = server_config['env'] - click.echo(f" Environment: {len(env_vars)} variables") - click.echo() + print(f"\nšŸš€ Next step: Restart AmazonQ to load the new MCP server") return 0 except Exception as e: - click.echo(f"āŒ Error listing servers: {e}", err=True) - return 1 - - def uninstall_server(self, server_name: str, config_location: str = "auto", - force: bool = False) -> int: - """Uninstall an MCP server from AmazonQ configuration.""" - try: - # Find MCP configuration - if config_location == "auto": - mcp_config_path = self.find_existing_mcp_config() - else: - mcp_config_path = self.get_mcp_config_paths().get(config_location) - - if not mcp_config_path or not mcp_config_path.exists(): - click.echo("āŒ No MCP configuration found") - return 1 - - # Load configuration - mcp_config = self.load_mcp_config(mcp_config_path) - servers = mcp_config.get("mcpServers", {}) - - if server_name not in servers: - click.echo(f"āŒ Server '{server_name}' not found in MCP configuration") - return 1 - - if not force: - if not click.confirm(f"Are you sure you want to uninstall server '{server_name}'?"): - click.echo("āŒ Uninstall cancelled") - return 1 - - # Remove server - del servers[server_name] - - # Save configuration - self.save_mcp_config(mcp_config, mcp_config_path) - - click.echo(f"āœ… Successfully uninstalled server '{server_name}'") - click.echo("šŸš€ Restart AmazonQ to apply changes") - - return 0 - - except Exception as e: - click.echo(f"āŒ Error uninstalling server: {e}", err=True) - return 1 - - -@click.group() -def amazonq(): - """AmazonQ MCP installation automation for ShellMCP servers.""" - pass - - -@amazonq.command() -@click.argument('yml_file', type=click.Path(exists=True)) -@click.argument('server_path', type=click.Path()) -@click.option('--config-location', '-l', - type=click.Choice(['global', 'local', 'user_config', 'auto']), - default='auto', - help='Where to save mcp.json configuration') -@click.option('--python-executable', '-p', - default='python3', - help='Python executable to use for running the server') -@click.option('--force', '-f', is_flag=True, - help='Overwrite existing server configuration') -def install(yml_file, server_path, config_location, python_executable, force): - """Install a ShellMCP server to AmazonQ mcp.json configuration.""" - installer = AmazonQInstaller() - exit_code = installer.install_server( - yml_file, server_path, config_location, python_executable, force - ) - sys.exit(exit_code) - - -@amazonq.command() -@click.option('--config-location', '-l', - type=click.Choice(['global', 'local', 'user_config', 'auto']), - default='auto', - help='MCP configuration location to check') -def list(config_location): - """List all installed MCP servers.""" - installer = AmazonQInstaller() - exit_code = installer.list_installed_servers(config_location) - sys.exit(exit_code) - - -@amazonq.command() -@click.argument('server_name') -@click.option('--config-location', '-l', - type=click.Choice(['global', 'local', 'user_config', 'auto']), - default='auto', - help='MCP configuration location to modify') -@click.option('--force', '-f', is_flag=True, - help='Skip confirmation prompt') -def uninstall(server_name, config_location, force): - """Uninstall an MCP server from AmazonQ configuration.""" - installer = AmazonQInstaller() - exit_code = installer.uninstall_server(server_name, config_location, force) - sys.exit(exit_code) - - -if __name__ == '__main__': - amazonq() \ No newline at end of file + print(f"āŒ Error adding server: {e}") + return 1 \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index 3994d7a..50d3b83 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -540,7 +540,7 @@ def add_prompt(config_file: str, name: str = None, prompt_name: str = None, desc def install_amazonq(config_file: str, server_path: str = None, config_location: str = "auto", python_executable: str = "python3", force: bool = False) -> int: """ - Install a ShellMCP server to AmazonQ mcp.json configuration. + Add a ShellMCP server to AmazonQ mcp.json configuration. Args: config_file: Path to the YAML configuration file @@ -573,45 +573,7 @@ def install_amazonq(config_file: str, server_path: str = None, config_location: ) except Exception as e: - return _handle_error(f"Error installing to AmazonQ: {e}", exception=e) - - -def list_amazonq_servers(config_location: str = "auto") -> int: - """ - List all installed AmazonQ MCP servers. - - Args: - config_location: MCP configuration location to check - - Returns: - Exit code (0 for success, 1 for failure) - """ - try: - installer = AmazonQInstaller() - return installer.list_installed_servers(config_location) - - except Exception as e: - return _handle_error(f"Error listing AmazonQ servers: {e}", exception=e) - - -def uninstall_amazonq_server(server_name: str, config_location: str = "auto", force: bool = False) -> int: - """ - Uninstall an AmazonQ MCP server. - - Args: - server_name: Name of the server to uninstall - config_location: MCP configuration location to modify - force: Skip confirmation prompt - - Returns: - Exit code (0 for success, 1 for failure) - """ - try: - installer = AmazonQInstaller() - return installer.uninstall_server(server_name, config_location, force) - - except Exception as e: - return _handle_error(f"Error uninstalling AmazonQ server: {e}", exception=e) + return _handle_error(f"Error adding to AmazonQ: {e}", exception=e) def main(): @@ -623,7 +585,5 @@ def main(): 'add-tool': add_tool, 'add-resource': add_resource, 'add-prompt': add_prompt, - 'install-amazonq': install_amazonq, - 'list-amazonq': list_amazonq_servers, - 'uninstall-amazonq': uninstall_amazonq_server + 'install-amazonq': install_amazonq }) \ No newline at end of file diff --git a/tests/test_amazonq_installer.py b/tests/test_amazonq_installer.py deleted file mode 100644 index 43d312d..0000000 --- a/tests/test_amazonq_installer.py +++ /dev/null @@ -1,246 +0,0 @@ -"""Tests for AmazonQ installer functionality.""" - -import json -import tempfile -from pathlib import Path -from unittest.mock import patch, MagicMock - -import pytest - -from shellmcp.amazonq_installer import AmazonQInstaller -from shellmcp.models import ServerConfig, YMLConfig - - -class TestAmazonQInstaller: - """Test cases for AmazonQInstaller.""" - - def setup_method(self): - """Set up test fixtures.""" - self.installer = AmazonQInstaller() - self.temp_dir = Path(tempfile.mkdtemp()) - - def teardown_method(self): - """Clean up test fixtures.""" - import shutil - shutil.rmtree(self.temp_dir, ignore_errors=True) - - def test_get_mcp_config_paths(self): - """Test MCP configuration path detection.""" - paths = self.installer.get_mcp_config_paths() - - assert 'global' in paths - assert 'local' in paths - assert 'user_config' in paths - - # Check that paths are Path objects - for path in paths.values(): - assert isinstance(path, Path) - - def test_create_default_mcp_config(self): - """Test default MCP configuration creation.""" - config = self.installer.create_default_mcp_config(self.temp_dir / "test.json") - - assert "mcpServers" in config - assert config["mcpServers"] == {} - - def test_load_mcp_config_existing(self): - """Test loading existing MCP configuration.""" - # Create test config file - config_path = self.temp_dir / "mcp.json" - test_config = { - "mcpServers": { - "test-server": { - "command": "python3", - "args": ["test.py"] - } - } - } - - with open(config_path, 'w') as f: - json.dump(test_config, f) - - # Load configuration - loaded_config = self.installer.load_mcp_config(config_path) - - assert loaded_config == test_config - - def test_load_mcp_config_new(self): - """Test creating new MCP configuration when file doesn't exist.""" - config_path = self.temp_dir / "nonexistent.json" - - config = self.installer.load_mcp_config(config_path) - - assert "mcpServers" in config - assert config["mcpServers"] == {} - - def test_save_mcp_config(self): - """Test saving MCP configuration.""" - config_path = self.temp_dir / "test.json" - test_config = { - "mcpServers": { - "test-server": { - "command": "python3", - "args": ["test.py"] - } - } - } - - self.installer.save_mcp_config(test_config, config_path) - - assert config_path.exists() - - with open(config_path, 'r') as f: - saved_config = json.load(f) - - assert saved_config == test_config - - def test_generate_server_config(self): - """Test server configuration generation.""" - # Create test YML config - yml_config = YMLConfig( - server=ServerConfig( - name="test-server", - desc="Test server", - version="1.0.0" - ) - ) - - server_path = "/path/to/server.py" - config = self.installer.generate_server_config(yml_config, server_path) - - assert config["command"] == "python3" - assert config["args"] == [server_path] - assert "env" in config - assert "PYTHONPATH" in config["env"] - - @patch('shellmcp.amazonq_installer.YMLParser') - def test_install_server_basic(self, mock_parser): - """Test basic server installation.""" - # Mock YML config - mock_config = YMLConfig( - server=ServerConfig( - name="test-server", - desc="Test server", - version="1.0.0" - ) - ) - mock_parser.return_value.load_from_file.return_value = mock_config - - # Create test files - yml_file = self.temp_dir / "test.yml" - server_file = self.temp_dir / "test_server.py" - - yml_file.write_text("test: config") - server_file.write_text("print('test server')") - - # Mock config path methods - with patch.object(self.installer, 'get_mcp_config_paths') as mock_paths, \ - patch.object(self.installer, 'load_mcp_config') as mock_load, \ - patch.object(self.installer, 'save_mcp_config') as mock_save: - - mock_paths.return_value = {'local': self.temp_dir / 'mcp.json'} - mock_load.return_value = {"mcpServers": {}} - - result = self.installer.install_server( - str(yml_file), - str(server_file), - config_location="local" - ) - - assert result == 0 - mock_save.assert_called_once() - - @patch('shellmcp.amazonq_installer.YMLParser') - def test_install_server_already_exists(self, mock_parser): - """Test server installation when server already exists.""" - # Mock YML config - mock_config = YMLConfig( - server=ServerConfig( - name="existing-server", - desc="Existing server", - version="1.0.0" - ) - ) - mock_parser.return_value.load_from_file.return_value = mock_config - - # Create test files - yml_file = self.temp_dir / "test.yml" - server_file = self.temp_dir / "test_server.py" - - yml_file.write_text("test: config") - server_file.write_text("print('test server')") - - # Mock config with existing server - existing_config = { - "mcpServers": { - "existing-server": { - "command": "python3", - "args": ["existing.py"] - } - } - } - - with patch.object(self.installer, 'get_mcp_config_paths') as mock_paths, \ - patch.object(self.installer, 'load_mcp_config') as mock_load: - - mock_paths.return_value = {'local': self.temp_dir / 'mcp.json'} - mock_load.return_value = existing_config - - result = self.installer.install_server( - str(yml_file), - str(server_file), - config_location="local", - force=False - ) - - assert result == 1 # Should fail due to existing server - - def test_list_installed_servers_no_config(self): - """Test listing servers when no config exists.""" - with patch.object(self.installer, 'find_existing_mcp_config') as mock_find: - mock_find.return_value = None - - result = self.installer.list_installed_servers() - - assert result == 1 - - def test_list_installed_servers_with_config(self): - """Test listing servers with existing config.""" - test_config = { - "mcpServers": { - "server1": { - "command": "python3", - "args": ["server1.py"] - }, - "server2": { - "command": "python3", - "args": ["server2.py"] - } - } - } - - config_path = self.temp_dir / "mcp.json" - - with patch.object(self.installer, 'find_existing_mcp_config') as mock_find, \ - patch.object(self.installer, 'load_mcp_config') as mock_load: - - mock_find.return_value = config_path - mock_load.return_value = test_config - - result = self.installer.list_installed_servers() - - assert result == 0 - - def test_uninstall_server_not_found(self): - """Test uninstalling a non-existent server.""" - config_path = self.temp_dir / "mcp.json" - - with patch.object(self.installer, 'find_existing_mcp_config') as mock_find, \ - patch.object(self.installer, 'load_mcp_config') as mock_load: - - mock_find.return_value = config_path - mock_load.return_value = {"mcpServers": {}} - - result = self.installer.uninstall_server("nonexistent-server") - - assert result == 1 \ No newline at end of file From a0123592d038aa0be007385867ae167cb690639c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 00:59:38 +0000 Subject: [PATCH 3/8] Refactor: Rename install-amazonq to mcp-json This change renames the `install-amazonq` command to `mcp-json` to better reflect its functionality. The command now generates MCP server configuration JSON for AmazonQ instead of directly installing it. This provides more flexibility and aligns with the typical workflow of copying generated configuration. The documentation and code have been updated accordingly. Co-authored-by: blakeinvictoria --- README.md | 26 ++-- docs/amazonq-integration.md | 122 ++++++++++------- examples/simple_mcp_example.py | 127 ++++++++++++++++++ shellmcp/amazonq_installer.py | 182 ++++++++------------------ shellmcp/cli.py | 39 +++--- shellmcp/templates/mcp_config.json.j2 | 11 ++ 6 files changed, 296 insertions(+), 211 deletions(-) create mode 100755 examples/simple_mcp_example.py create mode 100644 shellmcp/templates/mcp_config.json.j2 diff --git a/README.md b/README.md index 9290fa5..99bd3f2 100644 --- a/README.md +++ b/README.md @@ -145,32 +145,32 @@ Generate a FastMCP server from YAML configuration. shellmcp generate my-server.yml --output-dir ./output --verbose ``` -### `shellmcp install-amazonq` -Add a generated server to AmazonQ MCP configuration. +### `shellmcp mcp-json` +Generate MCP server configuration JSON for AmazonQ. ```bash -shellmcp install-amazonq my-server.yml -shellmcp install-amazonq my-server.yml --config-location global --force +shellmcp mcp-json my-server.yml +shellmcp mcp-json my-server.yml --output-file mcp.json ``` ## AmazonQ Integration -ShellMCP provides automated installation to AmazonQ's `mcp.json` configuration file: +ShellMCP can generate MCP server configuration JSON for AmazonQ: ```bash -# Complete workflow: create → generate → add to AmazonQ +# Complete workflow: create → generate → get MCP JSON shellmcp new --name "my-tools" --desc "My custom tools" shellmcp add-tool my_tools.yml shellmcp generate my_tools.yml -shellmcp install-amazonq my_tools.yml -# Restart AmazonQ to load your new server! +shellmcp mcp-json my_tools.yml +# Copy the JSON to your AmazonQ mcp.json file! ``` -The installer automatically: -- Detects existing AmazonQ MCP configurations -- Adds your server to the `mcp.json` file -- Handles multiple configuration locations (global/local/user) -- Overwrites existing servers with `--force` +The JSON generator: +- Auto-detects your generated server file +- Creates proper MCP server configuration +- Outputs to stdout or file +- Uses templates for consistent formatting See [AmazonQ Integration Guide](docs/amazonq-integration.md) for detailed documentation. diff --git a/docs/amazonq-integration.md b/docs/amazonq-integration.md index 94283fa..0ecf4a1 100644 --- a/docs/amazonq-integration.md +++ b/docs/amazonq-integration.md @@ -1,6 +1,6 @@ # AmazonQ Integration -ShellMCP provides simple automation for adding your generated servers to AmazonQ's `mcp.json` configuration file. +ShellMCP can generate MCP server configuration JSON for AmazonQ. This allows you to easily add your generated servers to AmazonQ's `mcp.json` configuration file. ## Quick Start @@ -16,44 +16,36 @@ ShellMCP provides simple automation for adding your generated servers to AmazonQ shellmcp generate my_tools.yml ``` -2. **Add to AmazonQ:** +2. **Generate MCP JSON configuration:** ```bash - # Auto-add to AmazonQ (detects existing config) - shellmcp install-amazonq my_tools.yml + # Output JSON to stdout + shellmcp mcp-json my_tools.yml + + # Or save to file + shellmcp mcp-json my_tools.yml --output-file mcp.json ``` -3. **Restart AmazonQ** to load your new MCP server! - -## Configuration Locations - -ShellMCP automatically detects and uses existing AmazonQ MCP configurations in the following locations: - -- **Global**: `~/.aws/amazonq/mcp.json` - Available system-wide -- **Local**: `./.amazonq/mcp.json` - Project-specific configuration -- **User Config**: `~/.config/amazonq/mcp.json` - User-specific configuration +3. **Add to AmazonQ** by copying the JSON to your `mcp.json` file ## Command Usage ```bash -# Add server to AmazonQ (auto-detects server path) -shellmcp install-amazonq my_tools.yml - -# Add with specific server path -shellmcp install-amazonq my_tools.yml ./output/my_tools_server.py +# Generate JSON and output to stdout +shellmcp mcp-json my_tools.yml -# Add to specific config location -shellmcp install-amazonq my_tools.yml --config-location global +# Generate JSON and save to file +shellmcp mcp-json my_tools.yml --output-file mcp.json -# Overwrite existing server -shellmcp install-amazonq my_tools.yml --force +# Use specific server path +shellmcp mcp-json my_tools.yml --server-path ./output/my_tools_server.py # Use different Python executable -shellmcp install-amazonq my_tools.yml --python-executable python3.11 +shellmcp mcp-json my_tools.yml --python-executable python3.11 ``` ## Example: File Manager Server -Here's a complete example of creating and adding a file manager server: +Here's a complete example of creating and generating MCP JSON for a file manager server: ### 1. Create Configuration @@ -100,15 +92,15 @@ tools: shellmcp generate file_manager.yml ``` -### 3. Add to AmazonQ +### 3. Generate MCP JSON ```bash -shellmcp install-amazonq file_manager.yml +shellmcp mcp-json file_manager.yml ``` ### 4. Generated MCP Configuration -The installer automatically adds to your `mcp.json`: +The command outputs: ```json { @@ -124,6 +116,53 @@ The installer automatically adds to your `mcp.json`: } ``` +## AmazonQ Configuration Locations + +AmazonQ looks for `mcp.json` in these locations: + +- **Global**: `~/.aws/amazonq/mcp.json` +- **Local**: `./.amazonq/mcp.json` +- **User Config**: `~/.config/amazonq/mcp.json` + +## Manual Integration + +1. **Generate the JSON:** + ```bash + shellmcp mcp-json my_tools.yml --output-file mcp_config.json + ``` + +2. **Copy to your AmazonQ config:** + ```bash + # For local project + mkdir -p .amazonq + cp mcp_config.json .amazonq/mcp.json + + # Or merge with existing config + # (manually edit your existing mcp.json to add the new server) + ``` + +3. **Restart AmazonQ** to load your new MCP server + +## Template Customization + +The MCP JSON is generated using a Jinja2 template located at `shellmcp/templates/mcp_config.json.j2`: + +```json +{ + "mcpServers": { + "{{ server_name }}": { + "command": "{{ python_executable }}", + "args": ["{{ server_path }}"], + "env": { + "PYTHONPATH": "{{ server_dir }}" + } + } + } +} +``` + +You can modify this template to customize the generated configuration format. + ## Troubleshooting ### Server Not Found @@ -132,29 +171,20 @@ If you get a "server file not found" error: 1. Ensure you've run `shellmcp generate` first 2. Check the server path is correct -3. Verify the generated server file exists - -### Configuration Not Loading - -If AmazonQ doesn't load your server: - -1. Restart AmazonQ after installation -2. Check the `mcp.json` file format is valid JSON -3. Verify the server path is accessible -4. Check AmazonQ logs for error messages +3. Use `--server-path` to specify the exact path -### Permission Issues +### JSON Format Issues -If you encounter permission issues: +If the generated JSON is invalid: -1. Ensure you have write access to the configuration directory -2. Try using `--config-location local` for project-specific configs -3. Check file permissions on the generated server files +1. Check that your YAML configuration is valid +2. Verify the server file exists at the specified path +3. Ensure file paths don't contain special characters ## Best Practices 1. **Use descriptive server names** - They become identifiers in AmazonQ -2. **Test your tools locally** - Validate your YAML configuration before installing -3. **Use version control** - Keep your YAML configurations in version control -4. **Document your tools** - Add clear descriptions for better AmazonQ integration -5. **Start simple** - Begin with basic tools and expand gradually \ No newline at end of file +2. **Test your tools locally** - Validate your YAML configuration before generating JSON +3. **Use absolute paths** - The generator automatically converts relative paths to absolute +4. **Version control** - Keep your YAML configurations in version control +5. **Document your tools** - Add clear descriptions for better AmazonQ integration \ No newline at end of file diff --git a/examples/simple_mcp_example.py b/examples/simple_mcp_example.py new file mode 100755 index 0000000..e5f064f --- /dev/null +++ b/examples/simple_mcp_example.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +""" +Simple example demonstrating ShellMCP MCP JSON generation. + +This script shows how to: +1. Create a ShellMCP server configuration +2. Generate the server code +3. Generate MCP JSON configuration for AmazonQ +""" + +import subprocess +import sys +from pathlib import Path + + +def create_simple_config(): + """Create a simple ShellMCP configuration.""" + config_content = """ +server: + name: "simple-tools" + desc: "Simple example tools for AmazonQ" + version: "1.0.0" + +tools: + hello_world: + cmd: "echo 'Hello from ShellMCP!'" + desc: "Simple hello world command" + + current_time: + cmd: "date" + desc: "Get current date and time" + help_cmd: "date --help" +""" + return config_content.strip() + + +def run_command(cmd, description): + """Run a command and handle errors.""" + print(f"šŸ”„ {description}...") + print(f" Command: {cmd}") + + try: + result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) + print(f"āœ… {description} completed successfully") + if result.stdout.strip(): + print(f" Output: {result.stdout.strip()}") + return True + except subprocess.CalledProcessError as e: + print(f"āŒ {description} failed") + print(f" Error: {e.stderr.strip()}") + return False + + +def main(): + """Main example workflow.""" + print("šŸš€ ShellMCP MCP JSON Generation Example") + print("=" * 45) + + # Create temporary directory for example + temp_dir = Path("example_output") + temp_dir.mkdir(exist_ok=True) + + config_file = temp_dir / "simple_tools.yml" + + print(f"šŸ“ Working in directory: {temp_dir}") + + # Step 1: Create configuration file + print("\nšŸ“ Step 1: Creating ShellMCP configuration...") + config_content = create_simple_config() + config_file.write_text(config_content) + print(f"āœ… Created configuration: {config_file}") + + # Step 2: Validate configuration + print("\nšŸ” Step 2: Validating configuration...") + if not run_command(f"python3 -m shellmcp.cli validate {config_file} --verbose", + "Configuration validation"): + print("āŒ Configuration validation failed") + return 1 + + # Step 3: Generate server + print("\nšŸ—ļø Step 3: Generating FastMCP server...") + if not run_command(f"python3 -m shellmcp.cli generate {config_file} --output-dir {temp_dir}/output --verbose", + "Server generation"): + print("āŒ Server generation failed") + return 1 + + # Step 4: Generate MCP JSON + print("\nšŸ”§ Step 4: Generating MCP JSON configuration...") + + # Find the generated server file + output_dir = temp_dir / "output" + server_files = list(output_dir.glob("*_server.py")) + + if not server_files: + print("āŒ No server file found in output directory") + return 1 + + server_file = server_files[0] + print(f"šŸ“„ Found server file: {server_file}") + + # Generate MCP JSON to stdout + print("\nšŸ“‹ Generated MCP JSON configuration:") + if not run_command(f"python3 -m shellmcp.cli mcp-json {config_file}", + "MCP JSON generation"): + print("āŒ MCP JSON generation failed") + return 1 + + # Generate MCP JSON to file + mcp_json_file = temp_dir / "mcp.json" + if not run_command(f"python3 -m shellmcp.cli mcp-json {config_file} --output-file {mcp_json_file}", + "MCP JSON file generation"): + print("āŒ MCP JSON file generation failed") + return 1 + + print(f"\nšŸ“„ MCP JSON saved to: {mcp_json_file}") + + print("\nšŸŽ‰ Example completed successfully!") + print("\nšŸ“– Next steps:") + print(" 1. Copy the generated JSON to your AmazonQ mcp.json file") + print(" 2. Restart AmazonQ to load your new MCP server") + print(f" 3. Your server will be available as 'simple-tools' in AmazonQ") + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/shellmcp/amazonq_installer.py b/shellmcp/amazonq_installer.py index f8481e1..32b6df6 100644 --- a/shellmcp/amazonq_installer.py +++ b/shellmcp/amazonq_installer.py @@ -1,165 +1,87 @@ -"""Simple AmazonQ MCP installation automation for ShellMCP servers.""" +"""Generate MCP server configuration JSON for AmazonQ.""" import json -import os from pathlib import Path from typing import Dict, Any, Optional +from jinja2 import Environment, FileSystemLoader + from .models import YMLConfig from .parser import YMLParser -class AmazonQInstaller: - """Simple installer for adding ShellMCP servers to AmazonQ mcp.json configuration.""" +class MCPConfigGenerator: + """Generate MCP server configuration JSON for AmazonQ.""" def __init__(self): self.parser = YMLParser() - - def get_mcp_config_paths(self) -> Dict[str, Path]: - """Get possible MCP configuration file paths for AmazonQ.""" - home = Path.home() - return { - 'global': home / '.aws' / 'amazonq' / 'mcp.json', - 'local': Path.cwd() / '.amazonq' / 'mcp.json', - 'user_config': home / '.config' / 'amazonq' / 'mcp.json' - } - - def find_existing_mcp_config(self) -> Optional[Path]: - """Find existing mcp.json configuration file.""" - paths = self.get_mcp_config_paths() - - for location, path in paths.items(): - if path.exists(): - print(f"šŸ“ Found existing MCP config at {location}: {path}") - return path - - return None - - def load_mcp_config(self, config_path: Path) -> Dict[str, Any]: - """Load existing MCP configuration or create new one.""" - if config_path.exists(): - try: - with open(config_path, 'r', encoding='utf-8') as f: - config = json.load(f) - print(f"āœ… Loaded existing MCP configuration from {config_path}") - return config - except (json.JSONDecodeError, Exception) as e: - print(f"āš ļø Error reading {config_path}: {e}") - print("Creating new configuration...") - - # Create new configuration - config = {"mcpServers": {}} - - # Ensure directory exists - config_path.parent.mkdir(parents=True, exist_ok=True) - - return config - - def save_mcp_config(self, config: Dict[str, Any], config_path: Path) -> None: - """Save MCP configuration to file.""" - # Ensure directory exists - config_path.parent.mkdir(parents=True, exist_ok=True) - - with open(config_path, 'w', encoding='utf-8') as f: - json.dump(config, f, indent=2, sort_keys=True) - - print(f"āœ… Saved MCP configuration to {config_path}") - - def generate_server_config(self, yml_config: YMLConfig, server_path: str, - python_executable: str = "python3") -> Dict[str, Any]: - """Generate MCP server configuration from ShellMCP YAML config.""" - # Determine if we should use absolute or relative path - if Path(server_path).is_absolute(): - command = server_path - else: - command = str(Path(server_path).resolve()) - - return { - "command": python_executable, - "args": [command], - "env": { - "PYTHONPATH": str(Path(server_path).parent) - } - } + # Set up Jinja2 environment with templates directory + template_dir = Path(__file__).parent / "templates" + self.jinja_env = Environment( + loader=FileSystemLoader(str(template_dir)), + trim_blocks=True, + lstrip_blocks=True + ) - def install_server(self, yml_file: str, server_path: str, - config_location: str = "auto", - python_executable: str = "python3", - force: bool = False) -> int: + def generate_mcp_json(self, yml_file: str, server_path: str = None, + python_executable: str = "python3", output_file: str = None) -> str: """ - Add a ShellMCP server to AmazonQ mcp.json configuration. + Generate MCP server configuration JSON. Args: yml_file: Path to ShellMCP YAML configuration file - server_path: Path to the generated server.py file - config_location: Where to save mcp.json (global/local/user_config/auto) + server_path: Path to the generated server.py file (auto-detected if not provided) python_executable: Python executable to use (default: python3) - force: Overwrite existing server configuration + output_file: Optional output file path (defaults to stdout) Returns: - Exit code (0 for success, 1 for failure) + Generated JSON configuration """ try: # Load ShellMCP configuration if not Path(yml_file).exists(): - print(f"āŒ YAML configuration file not found: {yml_file}") - return 1 + raise FileNotFoundError(f"YAML configuration file not found: {yml_file}") yml_config = self.parser.load_from_file(yml_file) server_name = yml_config.server.name.replace(' ', '-').replace('_', '-').lower() - # Determine MCP config location - if config_location == "auto": - existing_config = self.find_existing_mcp_config() - if existing_config: - mcp_config_path = existing_config - else: - # Default to local configuration - mcp_config_path = self.get_mcp_config_paths()['local'] - else: - mcp_config_path = self.get_mcp_config_paths().get(config_location) - if not mcp_config_path: - print(f"āŒ Invalid config location: {config_location}") - print(f"Valid options: {', '.join(self.get_mcp_config_paths().keys())}") - return 1 - - # Load or create MCP configuration - mcp_config = self.load_mcp_config(mcp_config_path) - - # Check if server already exists - if not mcp_config.get("mcpServers"): - mcp_config["mcpServers"] = {} + # Auto-detect server path if not provided + if server_path is None: + config_dir = Path(yml_file).parent + server_name_dir = yml_config.server.name.replace('-', '_').replace(' ', '_').lower() + server_path = config_dir / server_name_dir / f"{yml_config.server.name.replace('-', '_')}_server.py" - if server_name in mcp_config["mcpServers"] and not force: - print(f"āŒ Server '{server_name}' already exists in MCP configuration") - print("Use --force to overwrite existing configuration") - return 1 - - # Generate server configuration - server_config = self.generate_server_config(yml_config, server_path, python_executable) - - # Add server to configuration - mcp_config["mcpServers"][server_name] = server_config - - # Save configuration - self.save_mcp_config(mcp_config, mcp_config_path) + # Ensure server path is absolute + if not Path(server_path).is_absolute(): + server_path = str(Path(server_path).resolve()) + else: + server_path = str(server_path) - # Display success information - print(f"\nāœ… Successfully added server '{server_name}' to AmazonQ MCP configuration!") - print(f"šŸ“‹ Server Details:") - print(f" Name: {server_name}") - print(f" Description: {yml_config.server.desc}") - print(f" Version: {yml_config.server.version}") - print(f" Server file: {server_path}") - print(f" Config location: {mcp_config_path}") + # Get server directory for PYTHONPATH + server_dir = str(Path(server_path).parent) - if yml_config.tools: - print(f" Tools ({len(yml_config.tools)}): {', '.join(yml_config.tools.keys())}") + # Generate JSON using template + template = self.jinja_env.get_template('mcp_config.json.j2') + json_config = template.render( + server_name=server_name, + python_executable=python_executable, + server_path=server_path, + server_dir=server_dir + ) - print(f"\nšŸš€ Next step: Restart AmazonQ to load the new MCP server") + # Parse and pretty-print JSON + parsed_config = json.loads(json_config) + formatted_json = json.dumps(parsed_config, indent=2) - return 0 + # Write to file or return string + if output_file: + output_path = Path(output_file) + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, 'w', encoding='utf-8') as f: + f.write(formatted_json) + return f"āœ… MCP configuration written to {output_file}" + else: + return formatted_json except Exception as e: - print(f"āŒ Error adding server: {e}") - return 1 \ No newline at end of file + raise RuntimeError(f"Failed to generate MCP configuration: {e}") \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index 50d3b83..ce78fe7 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -18,7 +18,7 @@ ) from .parser import YMLParser from .utils import get_choice, get_input, get_yes_no, load_or_create_config, save_config -from .amazonq_installer import AmazonQInstaller +from .amazonq_installer import MCPConfigGenerator def _handle_error(error_msg: str, verbose: bool = False, exception: Exception = None) -> int: @@ -537,17 +537,16 @@ def add_prompt(config_file: str, name: str = None, prompt_name: str = None, desc return _handle_error(f"Error adding prompt: {e}", exception=e) -def install_amazonq(config_file: str, server_path: str = None, config_location: str = "auto", - python_executable: str = "python3", force: bool = False) -> int: +def mcp_json(config_file: str, server_path: str = None, python_executable: str = "python3", + output_file: str = None) -> int: """ - Add a ShellMCP server to AmazonQ mcp.json configuration. + Generate MCP server configuration JSON for AmazonQ. Args: config_file: Path to the YAML configuration file server_path: Path to the generated server.py file (auto-detected if not provided) - config_location: Where to save mcp.json (global/local/user_config/auto) python_executable: Python executable to use (default: python3) - force: Overwrite existing server configuration + output_file: Optional output file path (defaults to stdout) Returns: Exit code (0 for success, 1 for failure) @@ -556,24 +555,20 @@ def install_amazonq(config_file: str, server_path: str = None, config_location: if not _check_file_exists(config_file): return _handle_error(f"File '{config_file}' not found") - # Auto-detect server path if not provided - if server_path is None: - config_dir = Path(config_file).parent - parser = YMLParser() - config = parser.load_from_file(config_file) - server_name = config.server.name.replace('-', '_').replace(' ', '_').lower() - server_path = config_dir / server_name / f"{config.server.name.replace('-', '_')}_server.py" - - if not Path(server_path).exists(): - return _handle_error(f"Server file not found at {server_path}. Run 'shellmcp generate {config_file}' first.") - - installer = AmazonQInstaller() - return installer.install_server( - config_file, str(server_path), config_location, python_executable, force + generator = MCPConfigGenerator() + result = generator.generate_mcp_json( + config_file, server_path, python_executable, output_file ) + if output_file: + print(result) + else: + print(result) + + return 0 + except Exception as e: - return _handle_error(f"Error adding to AmazonQ: {e}", exception=e) + return _handle_error(f"Error generating MCP JSON: {e}", exception=e) def main(): @@ -585,5 +580,5 @@ def main(): 'add-tool': add_tool, 'add-resource': add_resource, 'add-prompt': add_prompt, - 'install-amazonq': install_amazonq + 'mcp-json': mcp_json }) \ No newline at end of file diff --git a/shellmcp/templates/mcp_config.json.j2 b/shellmcp/templates/mcp_config.json.j2 new file mode 100644 index 0000000..837b194 --- /dev/null +++ b/shellmcp/templates/mcp_config.json.j2 @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "{{ server_name }}": { + "command": "{{ python_executable }}", + "args": ["{{ server_path }}"], + "env": { + "PYTHONPATH": "{{ server_dir }}" + } + } + } +} \ No newline at end of file From 4a68264c49338f704bcb2bc96657f50655c752d3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 01:12:28 +0000 Subject: [PATCH 4/8] Refactor: Simplify MCP JSON generation logic Remove MCPConfigGenerator class and use a standalone function. Co-authored-by: blakeinvictoria --- examples/simple_mcp_example.py | 1 + pyproject.toml | 1 - shellmcp/amazonq_installer.py | 139 +++++++++++++++------------------ shellmcp/cli.py | 5 +- 4 files changed, 67 insertions(+), 79 deletions(-) diff --git a/examples/simple_mcp_example.py b/examples/simple_mcp_example.py index e5f064f..94e704c 100755 --- a/examples/simple_mcp_example.py +++ b/examples/simple_mcp_example.py @@ -11,6 +11,7 @@ import subprocess import sys from pathlib import Path +from shellmcp.amazonq_installer import generate_mcp_json def create_simple_config(): diff --git a/pyproject.toml b/pyproject.toml index 5dd8b1a..98e48da 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,6 @@ dependencies = [ "jinja2>=3.0.0", "fire>=0.5.0", "questionary>=2.0.0", - "click>=8.0.0", ] [project.optional-dependencies] diff --git a/shellmcp/amazonq_installer.py b/shellmcp/amazonq_installer.py index 32b6df6..da2e044 100644 --- a/shellmcp/amazonq_installer.py +++ b/shellmcp/amazonq_installer.py @@ -2,86 +2,75 @@ import json from pathlib import Path -from typing import Dict, Any, Optional from jinja2 import Environment, FileSystemLoader -from .models import YMLConfig from .parser import YMLParser -class MCPConfigGenerator: - """Generate MCP server configuration JSON for AmazonQ.""" +def generate_mcp_json(yml_file: str, server_path: str = None, + python_executable: str = "python3", output_file: str = None) -> str: + """ + Generate MCP server configuration JSON. - def __init__(self): - self.parser = YMLParser() - # Set up Jinja2 environment with templates directory - template_dir = Path(__file__).parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(str(template_dir)), - trim_blocks=True, - lstrip_blocks=True - ) + Args: + yml_file: Path to ShellMCP YAML configuration file + server_path: Path to the generated server.py file (auto-detected if not provided) + python_executable: Python executable to use (default: python3) + output_file: Optional output file path (defaults to stdout) - def generate_mcp_json(self, yml_file: str, server_path: str = None, - python_executable: str = "python3", output_file: str = None) -> str: - """ - Generate MCP server configuration JSON. - - Args: - yml_file: Path to ShellMCP YAML configuration file - server_path: Path to the generated server.py file (auto-detected if not provided) - python_executable: Python executable to use (default: python3) - output_file: Optional output file path (defaults to stdout) - - Returns: - Generated JSON configuration - """ - try: - # Load ShellMCP configuration - if not Path(yml_file).exists(): - raise FileNotFoundError(f"YAML configuration file not found: {yml_file}") - - yml_config = self.parser.load_from_file(yml_file) - server_name = yml_config.server.name.replace(' ', '-').replace('_', '-').lower() - - # Auto-detect server path if not provided - if server_path is None: - config_dir = Path(yml_file).parent - server_name_dir = yml_config.server.name.replace('-', '_').replace(' ', '_').lower() - server_path = config_dir / server_name_dir / f"{yml_config.server.name.replace('-', '_')}_server.py" - - # Ensure server path is absolute - if not Path(server_path).is_absolute(): - server_path = str(Path(server_path).resolve()) - else: - server_path = str(server_path) - - # Get server directory for PYTHONPATH - server_dir = str(Path(server_path).parent) - - # Generate JSON using template - template = self.jinja_env.get_template('mcp_config.json.j2') - json_config = template.render( - server_name=server_name, - python_executable=python_executable, - server_path=server_path, - server_dir=server_dir - ) - - # Parse and pretty-print JSON - parsed_config = json.loads(json_config) - formatted_json = json.dumps(parsed_config, indent=2) - - # Write to file or return string - if output_file: - output_path = Path(output_file) - output_path.parent.mkdir(parents=True, exist_ok=True) - with open(output_file, 'w', encoding='utf-8') as f: - f.write(formatted_json) - return f"āœ… MCP configuration written to {output_file}" - else: - return formatted_json - - except Exception as e: - raise RuntimeError(f"Failed to generate MCP configuration: {e}") \ No newline at end of file + Returns: + Generated JSON configuration + """ + # Load ShellMCP configuration + if not Path(yml_file).exists(): + raise FileNotFoundError(f"YAML configuration file not found: {yml_file}") + + parser = YMLParser() + yml_config = parser.load_from_file(yml_file) + server_name = yml_config.server.name.replace(' ', '-').replace('_', '-').lower() + + # Auto-detect server path if not provided + if server_path is None: + config_dir = Path(yml_file).parent + server_name_dir = yml_config.server.name.replace('-', '_').replace(' ', '_').lower() + server_path = config_dir / server_name_dir / f"{yml_config.server.name.replace('-', '_')}_server.py" + + # Ensure server path is absolute + if not Path(server_path).is_absolute(): + server_path = str(Path(server_path).resolve()) + else: + server_path = str(server_path) + + # Get server directory for PYTHONPATH + server_dir = str(Path(server_path).parent) + + # Set up Jinja2 environment and generate JSON using template + template_dir = Path(__file__).parent / "templates" + jinja_env = Environment( + loader=FileSystemLoader(str(template_dir)), + trim_blocks=True, + lstrip_blocks=True + ) + + template = jinja_env.get_template('mcp_config.json.j2') + json_config = template.render( + server_name=server_name, + python_executable=python_executable, + server_path=server_path, + server_dir=server_dir + ) + + # Parse and pretty-print JSON + parsed_config = json.loads(json_config) + formatted_json = json.dumps(parsed_config, indent=2) + + # Write to file or return string + if output_file: + output_path = Path(output_file) + output_path.parent.mkdir(parents=True, exist_ok=True) + with open(output_file, 'w', encoding='utf-8') as f: + f.write(formatted_json) + return f"āœ… MCP configuration written to {output_file}" + else: + return formatted_json \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index ce78fe7..b172589 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -18,7 +18,7 @@ ) from .parser import YMLParser from .utils import get_choice, get_input, get_yes_no, load_or_create_config, save_config -from .amazonq_installer import MCPConfigGenerator +from .amazonq_installer import generate_mcp_json def _handle_error(error_msg: str, verbose: bool = False, exception: Exception = None) -> int: @@ -555,8 +555,7 @@ def mcp_json(config_file: str, server_path: str = None, python_executable: str = if not _check_file_exists(config_file): return _handle_error(f"File '{config_file}' not found") - generator = MCPConfigGenerator() - result = generator.generate_mcp_json( + result = generate_mcp_json( config_file, server_path, python_executable, output_file ) From 772d4b0bd01a04aa7f817a4ddcfc56754dffd6cc Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 02:58:30 +0000 Subject: [PATCH 5/8] Refactor: Rename mcp-json to mcp-config and update docs Co-authored-by: blakeinvictoria --- README.md | 24 ++-- ...zonq-integration.md => mcp-integration.md} | 55 ++++---- examples/simple_mcp_example.py | 128 ------------------ shellmcp/cli.py | 14 +- .../{amazonq_installer.py => mcp_config.py} | 6 +- 5 files changed, 49 insertions(+), 178 deletions(-) rename docs/{amazonq-integration.md => mcp-integration.md} (67%) delete mode 100755 examples/simple_mcp_example.py rename shellmcp/{amazonq_installer.py => mcp_config.py} (92%) diff --git a/README.md b/README.md index 99bd3f2..6f5310d 100644 --- a/README.md +++ b/README.md @@ -145,39 +145,39 @@ Generate a FastMCP server from YAML configuration. shellmcp generate my-server.yml --output-dir ./output --verbose ``` -### `shellmcp mcp-json` -Generate MCP server configuration JSON for AmazonQ. +### `shellmcp mcp-config` +Generate MCP server configuration JSON. ```bash -shellmcp mcp-json my-server.yml -shellmcp mcp-json my-server.yml --output-file mcp.json +shellmcp mcp-config my-server.yml +shellmcp mcp-config my-server.yml --output-file mcp.json ``` -## AmazonQ Integration +## MCP Configuration -ShellMCP can generate MCP server configuration JSON for AmazonQ: +ShellMCP can generate MCP server configuration JSON: ```bash -# Complete workflow: create → generate → get MCP JSON +# Complete workflow: create → generate → get MCP config shellmcp new --name "my-tools" --desc "My custom tools" shellmcp add-tool my_tools.yml shellmcp generate my_tools.yml -shellmcp mcp-json my_tools.yml -# Copy the JSON to your AmazonQ mcp.json file! +shellmcp mcp-config my_tools.yml +# Copy the JSON to your MCP client configuration! ``` -The JSON generator: +The MCP config generator: - Auto-detects your generated server file - Creates proper MCP server configuration - Outputs to stdout or file - Uses templates for consistent formatting -See [AmazonQ Integration Guide](docs/amazonq-integration.md) for detailed documentation. +See [MCP Integration Guide](docs/mcp-integration.md) for detailed documentation. ## Documentation - [YAML Specification](docs/yml-specification.md) -- [AmazonQ Integration](docs/amazonq-integration.md) +- [MCP Integration](docs/mcp-integration.md) ## License diff --git a/docs/amazonq-integration.md b/docs/mcp-integration.md similarity index 67% rename from docs/amazonq-integration.md rename to docs/mcp-integration.md index 0ecf4a1..6a1586f 100644 --- a/docs/amazonq-integration.md +++ b/docs/mcp-integration.md @@ -1,13 +1,13 @@ -# AmazonQ Integration +# MCP Integration -ShellMCP can generate MCP server configuration JSON for AmazonQ. This allows you to easily add your generated servers to AmazonQ's `mcp.json` configuration file. +ShellMCP can generate MCP server configuration JSON. This allows you to easily add your generated servers to MCP client configuration files. ## Quick Start 1. **Create and generate your ShellMCP server:** ```bash # Create a new server configuration - shellmcp new --name "my-tools" --desc "My custom tools for AmazonQ" + shellmcp new --name "my-tools" --desc "My custom tools" # Add some tools shellmcp add-tool my_tools.yml @@ -16,36 +16,36 @@ ShellMCP can generate MCP server configuration JSON for AmazonQ. This allows you shellmcp generate my_tools.yml ``` -2. **Generate MCP JSON configuration:** +2. **Generate MCP configuration:** ```bash # Output JSON to stdout - shellmcp mcp-json my_tools.yml + shellmcp mcp-config my_tools.yml # Or save to file - shellmcp mcp-json my_tools.yml --output-file mcp.json + shellmcp mcp-config my_tools.yml --output-file mcp.json ``` -3. **Add to AmazonQ** by copying the JSON to your `mcp.json` file +3. **Add to your MCP client** by copying the JSON to your configuration file ## Command Usage ```bash # Generate JSON and output to stdout -shellmcp mcp-json my_tools.yml +shellmcp mcp-config my_tools.yml # Generate JSON and save to file -shellmcp mcp-json my_tools.yml --output-file mcp.json +shellmcp mcp-config my_tools.yml --output-file mcp.json # Use specific server path -shellmcp mcp-json my_tools.yml --server-path ./output/my_tools_server.py +shellmcp mcp-config my_tools.yml --server-path ./output/my_tools_server.py # Use different Python executable -shellmcp mcp-json my_tools.yml --python-executable python3.11 +shellmcp mcp-config my_tools.yml --python-executable python3.11 ``` ## Example: File Manager Server -Here's a complete example of creating and generating MCP JSON for a file manager server: +Here's a complete example of creating and generating MCP configuration for a file manager server: ### 1. Create Configuration @@ -92,10 +92,10 @@ tools: shellmcp generate file_manager.yml ``` -### 3. Generate MCP JSON +### 3. Generate MCP Configuration ```bash -shellmcp mcp-json file_manager.yml +shellmcp mcp-config file_manager.yml ``` ### 4. Generated MCP Configuration @@ -116,32 +116,31 @@ The command outputs: } ``` -## AmazonQ Configuration Locations +## MCP Configuration Locations -AmazonQ looks for `mcp.json` in these locations: +MCP clients typically look for configuration in these locations: -- **Global**: `~/.aws/amazonq/mcp.json` -- **Local**: `./.amazonq/mcp.json` -- **User Config**: `~/.config/amazonq/mcp.json` +- **Global**: `~/.config/mcp/config.json` +- **Local**: `./mcp.json` +- **User Config**: `~/.mcp/config.json` ## Manual Integration -1. **Generate the JSON:** +1. **Generate the configuration:** ```bash - shellmcp mcp-json my_tools.yml --output-file mcp_config.json + shellmcp mcp-config my_tools.yml --output-file mcp_config.json ``` -2. **Copy to your AmazonQ config:** +2. **Copy to your MCP client config:** ```bash # For local project - mkdir -p .amazonq - cp mcp_config.json .amazonq/mcp.json + cp mcp_config.json ./mcp.json # Or merge with existing config - # (manually edit your existing mcp.json to add the new server) + # (manually edit your existing config to add the new server) ``` -3. **Restart AmazonQ** to load your new MCP server +3. **Restart your MCP client** to load your new MCP server ## Template Customization @@ -183,8 +182,8 @@ If the generated JSON is invalid: ## Best Practices -1. **Use descriptive server names** - They become identifiers in AmazonQ +1. **Use descriptive server names** - They become identifiers in your MCP client 2. **Test your tools locally** - Validate your YAML configuration before generating JSON 3. **Use absolute paths** - The generator automatically converts relative paths to absolute 4. **Version control** - Keep your YAML configurations in version control -5. **Document your tools** - Add clear descriptions for better AmazonQ integration \ No newline at end of file +5. **Document your tools** - Add clear descriptions for better MCP integration \ No newline at end of file diff --git a/examples/simple_mcp_example.py b/examples/simple_mcp_example.py deleted file mode 100755 index 94e704c..0000000 --- a/examples/simple_mcp_example.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python3 -""" -Simple example demonstrating ShellMCP MCP JSON generation. - -This script shows how to: -1. Create a ShellMCP server configuration -2. Generate the server code -3. Generate MCP JSON configuration for AmazonQ -""" - -import subprocess -import sys -from pathlib import Path -from shellmcp.amazonq_installer import generate_mcp_json - - -def create_simple_config(): - """Create a simple ShellMCP configuration.""" - config_content = """ -server: - name: "simple-tools" - desc: "Simple example tools for AmazonQ" - version: "1.0.0" - -tools: - hello_world: - cmd: "echo 'Hello from ShellMCP!'" - desc: "Simple hello world command" - - current_time: - cmd: "date" - desc: "Get current date and time" - help_cmd: "date --help" -""" - return config_content.strip() - - -def run_command(cmd, description): - """Run a command and handle errors.""" - print(f"šŸ”„ {description}...") - print(f" Command: {cmd}") - - try: - result = subprocess.run(cmd, shell=True, capture_output=True, text=True, check=True) - print(f"āœ… {description} completed successfully") - if result.stdout.strip(): - print(f" Output: {result.stdout.strip()}") - return True - except subprocess.CalledProcessError as e: - print(f"āŒ {description} failed") - print(f" Error: {e.stderr.strip()}") - return False - - -def main(): - """Main example workflow.""" - print("šŸš€ ShellMCP MCP JSON Generation Example") - print("=" * 45) - - # Create temporary directory for example - temp_dir = Path("example_output") - temp_dir.mkdir(exist_ok=True) - - config_file = temp_dir / "simple_tools.yml" - - print(f"šŸ“ Working in directory: {temp_dir}") - - # Step 1: Create configuration file - print("\nšŸ“ Step 1: Creating ShellMCP configuration...") - config_content = create_simple_config() - config_file.write_text(config_content) - print(f"āœ… Created configuration: {config_file}") - - # Step 2: Validate configuration - print("\nšŸ” Step 2: Validating configuration...") - if not run_command(f"python3 -m shellmcp.cli validate {config_file} --verbose", - "Configuration validation"): - print("āŒ Configuration validation failed") - return 1 - - # Step 3: Generate server - print("\nšŸ—ļø Step 3: Generating FastMCP server...") - if not run_command(f"python3 -m shellmcp.cli generate {config_file} --output-dir {temp_dir}/output --verbose", - "Server generation"): - print("āŒ Server generation failed") - return 1 - - # Step 4: Generate MCP JSON - print("\nšŸ”§ Step 4: Generating MCP JSON configuration...") - - # Find the generated server file - output_dir = temp_dir / "output" - server_files = list(output_dir.glob("*_server.py")) - - if not server_files: - print("āŒ No server file found in output directory") - return 1 - - server_file = server_files[0] - print(f"šŸ“„ Found server file: {server_file}") - - # Generate MCP JSON to stdout - print("\nšŸ“‹ Generated MCP JSON configuration:") - if not run_command(f"python3 -m shellmcp.cli mcp-json {config_file}", - "MCP JSON generation"): - print("āŒ MCP JSON generation failed") - return 1 - - # Generate MCP JSON to file - mcp_json_file = temp_dir / "mcp.json" - if not run_command(f"python3 -m shellmcp.cli mcp-json {config_file} --output-file {mcp_json_file}", - "MCP JSON file generation"): - print("āŒ MCP JSON file generation failed") - return 1 - - print(f"\nšŸ“„ MCP JSON saved to: {mcp_json_file}") - - print("\nšŸŽ‰ Example completed successfully!") - print("\nšŸ“– Next steps:") - print(" 1. Copy the generated JSON to your AmazonQ mcp.json file") - print(" 2. Restart AmazonQ to load your new MCP server") - print(f" 3. Your server will be available as 'simple-tools' in AmazonQ") - - return 0 - - -if __name__ == "__main__": - sys.exit(main()) \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index b172589..3a212f6 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -18,7 +18,7 @@ ) from .parser import YMLParser from .utils import get_choice, get_input, get_yes_no, load_or_create_config, save_config -from .amazonq_installer import generate_mcp_json +from .mcp_config import generate_mcp_config def _handle_error(error_msg: str, verbose: bool = False, exception: Exception = None) -> int: @@ -537,10 +537,10 @@ def add_prompt(config_file: str, name: str = None, prompt_name: str = None, desc return _handle_error(f"Error adding prompt: {e}", exception=e) -def mcp_json(config_file: str, server_path: str = None, python_executable: str = "python3", - output_file: str = None) -> int: +def mcp_config(config_file: str, server_path: str = None, python_executable: str = "python3", + output_file: str = None) -> int: """ - Generate MCP server configuration JSON for AmazonQ. + Generate MCP server configuration JSON. Args: config_file: Path to the YAML configuration file @@ -555,7 +555,7 @@ def mcp_json(config_file: str, server_path: str = None, python_executable: str = if not _check_file_exists(config_file): return _handle_error(f"File '{config_file}' not found") - result = generate_mcp_json( + result = generate_mcp_config( config_file, server_path, python_executable, output_file ) @@ -567,7 +567,7 @@ def mcp_json(config_file: str, server_path: str = None, python_executable: str = return 0 except Exception as e: - return _handle_error(f"Error generating MCP JSON: {e}", exception=e) + return _handle_error(f"Error generating MCP config: {e}", exception=e) def main(): @@ -579,5 +579,5 @@ def main(): 'add-tool': add_tool, 'add-resource': add_resource, 'add-prompt': add_prompt, - 'mcp-json': mcp_json + 'mcp-config': mcp_config }) \ No newline at end of file diff --git a/shellmcp/amazonq_installer.py b/shellmcp/mcp_config.py similarity index 92% rename from shellmcp/amazonq_installer.py rename to shellmcp/mcp_config.py index da2e044..27af213 100644 --- a/shellmcp/amazonq_installer.py +++ b/shellmcp/mcp_config.py @@ -1,4 +1,4 @@ -"""Generate MCP server configuration JSON for AmazonQ.""" +"""Generate MCP server configuration JSON.""" import json from pathlib import Path @@ -8,8 +8,8 @@ from .parser import YMLParser -def generate_mcp_json(yml_file: str, server_path: str = None, - python_executable: str = "python3", output_file: str = None) -> str: +def generate_mcp_config(yml_file: str, server_path: str = None, + python_executable: str = "python3", output_file: str = None) -> str: """ Generate MCP server configuration JSON. From 93267d094afbe1b8e849522f3611f407cfeb7ced Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 14:15:13 +0000 Subject: [PATCH 6/8] Remove example file manager server from docs Co-authored-by: blakeinvictoria --- docs/mcp-integration.md | 72 ----------------------------------------- 1 file changed, 72 deletions(-) diff --git a/docs/mcp-integration.md b/docs/mcp-integration.md index 6a1586f..61143b1 100644 --- a/docs/mcp-integration.md +++ b/docs/mcp-integration.md @@ -43,78 +43,6 @@ shellmcp mcp-config my_tools.yml --server-path ./output/my_tools_server.py shellmcp mcp-config my_tools.yml --python-executable python3.11 ``` -## Example: File Manager Server - -Here's a complete example of creating and generating MCP configuration for a file manager server: - -### 1. Create Configuration - -```bash -shellmcp new --name "file-manager" --desc "File system operations" --version "1.0.0" -``` - -This creates `file_manager.yml`: - -```yaml -server: - name: "file-manager" - desc: "File system operations" - version: "1.0.0" - -args: - path_arg: - help: "Directory path" - type: string - default: "." - -tools: - list_files: - cmd: "ls -la {{path}}" - desc: "List files in a directory" - args: - - name: path - ref: path_arg - - search_files: - cmd: "find {{path}} -name '{{pattern}}' -type f" - desc: "Search for files matching a pattern" - args: - - name: path - ref: path_arg - - name: pattern - help: "Search pattern" - type: string -``` - -### 2. Generate Server - -```bash -shellmcp generate file_manager.yml -``` - -### 3. Generate MCP Configuration - -```bash -shellmcp mcp-config file_manager.yml -``` - -### 4. Generated MCP Configuration - -The command outputs: - -```json -{ - "mcpServers": { - "file-manager": { - "command": "python3", - "args": ["/path/to/file_manager/file_manager_server.py"], - "env": { - "PYTHONPATH": "/path/to/file_manager" - } - } - } -} -``` ## MCP Configuration Locations From 39120962429af8bf86ce2b46c1ec82cfeb23198c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 14:34:58 +0000 Subject: [PATCH 7/8] feat: Add --allow-auto-confirm flag to mcp-config Co-authored-by: blakeinvictoria --- README.md | 1 + docs/mcp-integration.md | 33 ++++++++++++++++++++++++++- shellmcp/cli.py | 5 ++-- shellmcp/mcp_config.py | 7 ++++-- shellmcp/templates/mcp_config.json.j2 | 3 ++- 5 files changed, 43 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6f5310d..16bc780 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,7 @@ Generate MCP server configuration JSON. ```bash shellmcp mcp-config my-server.yml shellmcp mcp-config my-server.yml --output-file mcp.json +shellmcp mcp-config my-server.yml --allow-auto-confirm ``` ## MCP Configuration diff --git a/docs/mcp-integration.md b/docs/mcp-integration.md index 61143b1..316906e 100644 --- a/docs/mcp-integration.md +++ b/docs/mcp-integration.md @@ -41,6 +41,9 @@ shellmcp mcp-config my_tools.yml --server-path ./output/my_tools_server.py # Use different Python executable shellmcp mcp-config my_tools.yml --python-executable python3.11 + +# Enable auto-trusting for tools +shellmcp mcp-config my_tools.yml --allow-auto-confirm ``` @@ -70,6 +73,33 @@ MCP clients typically look for configuration in these locations: 3. **Restart your MCP client** to load your new MCP server +## Auto-Trusting Tools + +You can enable auto-trusting for your tools using the `--allow-auto-confirm` flag. This adds the `allowAutoConfirm` property to the MCP configuration, allowing tools to run without user confirmation: + +```bash +shellmcp mcp-config my_tools.yml --allow-auto-confirm +``` + +This generates: + +```json +{ + "mcpServers": { + "my-tools": { + "command": "python3", + "args": ["/path/to/my_tools_server.py"], + "env": { + "PYTHONPATH": "/path/to/server/directory" + }, + "allowAutoConfirm": true + } + } +} +``` + +**Security Note**: Only enable auto-trusting for tools you fully trust, as this allows them to run without user confirmation. + ## Template Customization The MCP JSON is generated using a Jinja2 template located at `shellmcp/templates/mcp_config.json.j2`: @@ -82,7 +112,8 @@ The MCP JSON is generated using a Jinja2 template located at `shellmcp/templates "args": ["{{ server_path }}"], "env": { "PYTHONPATH": "{{ server_dir }}" - } + }{% if allow_auto_confirm %}, + "allowAutoConfirm": {{ allow_auto_confirm|lower }}{% endif %} } } } diff --git a/shellmcp/cli.py b/shellmcp/cli.py index 3a212f6..7dd83ae 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -538,7 +538,7 @@ def add_prompt(config_file: str, name: str = None, prompt_name: str = None, desc def mcp_config(config_file: str, server_path: str = None, python_executable: str = "python3", - output_file: str = None) -> int: + output_file: str = None, allow_auto_confirm: bool = False) -> int: """ Generate MCP server configuration JSON. @@ -547,6 +547,7 @@ def mcp_config(config_file: str, server_path: str = None, python_executable: str server_path: Path to the generated server.py file (auto-detected if not provided) python_executable: Python executable to use (default: python3) output_file: Optional output file path (defaults to stdout) + allow_auto_confirm: Enable auto-trusting for tools (default: False) Returns: Exit code (0 for success, 1 for failure) @@ -556,7 +557,7 @@ def mcp_config(config_file: str, server_path: str = None, python_executable: str return _handle_error(f"File '{config_file}' not found") result = generate_mcp_config( - config_file, server_path, python_executable, output_file + config_file, server_path, python_executable, output_file, allow_auto_confirm ) if output_file: diff --git a/shellmcp/mcp_config.py b/shellmcp/mcp_config.py index 27af213..2b7d345 100644 --- a/shellmcp/mcp_config.py +++ b/shellmcp/mcp_config.py @@ -9,7 +9,8 @@ def generate_mcp_config(yml_file: str, server_path: str = None, - python_executable: str = "python3", output_file: str = None) -> str: + python_executable: str = "python3", output_file: str = None, + allow_auto_confirm: bool = False) -> str: """ Generate MCP server configuration JSON. @@ -18,6 +19,7 @@ def generate_mcp_config(yml_file: str, server_path: str = None, server_path: Path to the generated server.py file (auto-detected if not provided) python_executable: Python executable to use (default: python3) output_file: Optional output file path (defaults to stdout) + allow_auto_confirm: Enable auto-trusting for tools (default: False) Returns: Generated JSON configuration @@ -58,7 +60,8 @@ def generate_mcp_config(yml_file: str, server_path: str = None, server_name=server_name, python_executable=python_executable, server_path=server_path, - server_dir=server_dir + server_dir=server_dir, + allow_auto_confirm=allow_auto_confirm ) # Parse and pretty-print JSON diff --git a/shellmcp/templates/mcp_config.json.j2 b/shellmcp/templates/mcp_config.json.j2 index 837b194..df0646f 100644 --- a/shellmcp/templates/mcp_config.json.j2 +++ b/shellmcp/templates/mcp_config.json.j2 @@ -5,7 +5,8 @@ "args": ["{{ server_path }}"], "env": { "PYTHONPATH": "{{ server_dir }}" - } + }{% if allow_auto_confirm %}, + "allowAutoConfirm": {{ allow_auto_confirm|lower }}{% endif %} } } } \ No newline at end of file From e5ef080f196047d643f1887c4276513f98f36fb3 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 14 Sep 2025 15:33:29 +0000 Subject: [PATCH 8/8] Refactor auto-trusting to use --no-confirm-dangerous flag Co-authored-by: blakeinvictoria --- docs/mcp-integration.md | 12 +++++------- shellmcp/templates/mcp_config.json.j2 | 5 ++--- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/docs/mcp-integration.md b/docs/mcp-integration.md index 316906e..ae2eb42 100644 --- a/docs/mcp-integration.md +++ b/docs/mcp-integration.md @@ -75,7 +75,7 @@ MCP clients typically look for configuration in these locations: ## Auto-Trusting Tools -You can enable auto-trusting for your tools using the `--allow-auto-confirm` flag. This adds the `allowAutoConfirm` property to the MCP configuration, allowing tools to run without user confirmation: +You can enable auto-trusting for your tools using the `--allow-auto-confirm` flag. This adds the `--no-confirm-dangerous` argument to the server command, allowing tools to run without user confirmation: ```bash shellmcp mcp-config my_tools.yml --allow-auto-confirm @@ -88,11 +88,10 @@ This generates: "mcpServers": { "my-tools": { "command": "python3", - "args": ["/path/to/my_tools_server.py"], + "args": ["--no-confirm-dangerous", "/path/to/my_tools_server.py"], "env": { "PYTHONPATH": "/path/to/server/directory" - }, - "allowAutoConfirm": true + } } } } @@ -109,11 +108,10 @@ The MCP JSON is generated using a Jinja2 template located at `shellmcp/templates "mcpServers": { "{{ server_name }}": { "command": "{{ python_executable }}", - "args": ["{{ server_path }}"], + "args": [{% if allow_auto_confirm %}"--no-confirm-dangerous", {% endif %}"{{ server_path }}"], "env": { "PYTHONPATH": "{{ server_dir }}" - }{% if allow_auto_confirm %}, - "allowAutoConfirm": {{ allow_auto_confirm|lower }}{% endif %} + } } } } diff --git a/shellmcp/templates/mcp_config.json.j2 b/shellmcp/templates/mcp_config.json.j2 index df0646f..3e2322c 100644 --- a/shellmcp/templates/mcp_config.json.j2 +++ b/shellmcp/templates/mcp_config.json.j2 @@ -2,11 +2,10 @@ "mcpServers": { "{{ server_name }}": { "command": "{{ python_executable }}", - "args": ["{{ server_path }}"], + "args": [{% if allow_auto_confirm %}"--no-confirm-dangerous", {% endif %}"{{ server_path }}"], "env": { "PYTHONPATH": "{{ server_dir }}" - }{% if allow_auto_confirm %}, - "allowAutoConfirm": {{ allow_auto_confirm|lower }}{% endif %} + } } } } \ No newline at end of file