diff --git a/README.md b/README.md index 2d53154..ed52ddc 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ShellMCP is a powerful tool that allows you to easily create Model Context Protocol (MCP) servers by exposing shell commands as structured tools. Instead of granting AI agents full shell access (which poses security risks), ShellMCP enables you to expose only the specific commands you trust, allowing agents to work autonomously with a predefined set of safe operations. -Define your tools in YAML, and ShellMCP generates a complete FastMCP server for you. +Define your tools in YAML, and ShellMCP generates and runs a complete FastMCP server for you. ## Quick Start @@ -12,7 +12,10 @@ Define your tools in YAML, and ShellMCP generates a complete FastMCP server for # Install ShellMCP pip install shellmcp -# Create a new server configuration +# Run a built-in MCP server directly +shellmcp run basics + +# Or create a custom server configuration shellmcp new --name "my-server" --desc "My custom MCP server" # Add a tool interactively @@ -23,18 +26,22 @@ shellmcp validate my-server.yml # Generate the FastMCP server shellmcp generate my-server.yml + +# Or run directly from a YAML file +shellmcp run --config_file my-server.yml ``` ## Features -- 🚀 **Simple YAML Configuration**: Define tools, resources, and prompts in clean YAML +- 🚀 **Simple YAML Configuration**: Define tools and resources in clean YAML - 🔧 **Interactive CLI**: Add tools and resources with guided prompts - 📝 **Template Support**: Use Jinja2 templates for dynamic command generation - ✅ **Validation**: Built-in configuration validation and error checking -- 🎯 **FastMCP Integration**: Generates production-ready FastMCP servers -- 📦 **Complete Output**: Includes server code, requirements, and documentation +- 🎯 **FastMCP Integration**: Generates and runs production-ready FastMCP servers +- 📦 **Built-in Configurations**: Pre-configured servers ready to run - 🔒 **Security-First**: Expose only trusted commands to AI agents -- 🎨 **Flexible**: Support for tools, resources, and prompts with reusable arguments +- 🎨 **Flexible**: Support for tools and resources with reusable arguments +- ⚡ **Instant Execution**: Run servers directly from YAML without generating files ## Example @@ -103,6 +110,28 @@ prompts: ShellMCP provides several commands to help you create and manage MCP servers: +### `shellmcp run` +Run an MCP server directly from a built-in configuration or YAML file. + +```bash +# Run a built-in configuration +shellmcp run basics + +# Run from a custom YAML file +shellmcp run --config_file my-server.yml +``` + +Built-in configurations: +- **basics**: Basic shell operations for file system, process management, and system information + +The `basics` configuration includes tools for: +- File operations (list, find, copy, move, delete) +- Directory management (create, remove, navigate) +- Process management (list, kill processes) +- System information (memory, disk, network) +- Text operations (read, write, search) +- Resources for system status and environment info + ### `shellmcp new` Create a new server configuration file. diff --git a/pyproject.toml b/pyproject.toml index 98e48da..566709b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "shellmcp" -version = "1.0.0" +version = "1.1.0" description = "Expose Shell Commands as MCP tools" readme = "README.md" requires-python = ">=3.8" @@ -14,6 +14,7 @@ dependencies = [ "jinja2>=3.0.0", "fire>=0.5.0", "questionary>=2.0.0", + "fastmcp>=0.1.0", ] [project.optional-dependencies] @@ -30,7 +31,7 @@ where = ["."] include = ["shellmcp*"] [tool.setuptools.package-data] -shellmcp = ["templates/*.j2"] +shellmcp = ["templates/*.j2", "configs/*.yml"] [project.scripts] shellmcp = "shellmcp.cli:main" diff --git a/shellmcp/__init__.py b/shellmcp/__init__.py index f729aa2..c6cb755 100644 --- a/shellmcp/__init__.py +++ b/shellmcp/__init__.py @@ -1,3 +1,3 @@ """ShellMCP - Expose Shell Commands as MCP tools.""" -__version__ = "1.0.0" \ No newline at end of file +__version__ = "1.1.0" \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index 7a8c633..d86b2bc 100644 --- a/shellmcp/cli.py +++ b/shellmcp/cli.py @@ -17,9 +17,23 @@ YMLConfig, ) from .parser import YMLParser +from .generator import FastMCPGenerator from .utils import get_choice, get_input, get_yes_no, load_or_create_config, save_config +def _get_builtin_config(config_name: str) -> str: + """Get path to a built-in configuration file.""" + from pathlib import Path + config_dir = Path(__file__).parent / "configs" + config_file = config_dir / f"{config_name}.yml" + + if not config_file.exists(): + available_configs = [f.stem for f in config_dir.glob("*.yml")] + raise ValueError(f"Built-in config '{config_name}' not found. Available configs: {', '.join(available_configs)}") + + return str(config_file) + + def _handle_error(error_msg: str, verbose: bool = False, exception: Exception = None) -> int: """Common error handling for CLI functions.""" print(f"❌ {error_msg}", file=sys.stderr) @@ -538,6 +552,81 @@ 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 run(config_name: str = None, config_file: str = None) -> int: + """ + Run an MCP server from a built-in configuration or YAML file. + + Args: + config_name: Name of built-in configuration (e.g., 'basics') + config_file: Path to YAML configuration file + + Returns: + Exit code (0 for success, 1 for failure) + """ + try: + if config_name and config_file: + return _handle_error("Cannot specify both config_name and config_file. Use one or the other.") + + if not config_name and not config_file: + return _handle_error("Must specify either config_name (for built-in configs) or config_file (for custom configs)") + + # Determine which config to use + if config_name: + # Use built-in configuration + try: + config_path = _get_builtin_config(config_name) + print(f"🚀 Starting built-in MCP server: {config_name}") + print(f"📁 Configuration: {config_path}") + except ValueError as e: + return _handle_error(str(e)) + else: + # Use custom configuration file + if not _check_file_exists(config_file): + return _handle_error(f"Configuration file '{config_file}' not found") + + config_path = config_file + print(f"🚀 Starting MCP server from configuration: {config_file}") + + # Generate and run the server + _generate_and_run_server(config_path) + return 0 + + except Exception as e: + return _handle_error(f"Error running MCP server: {e}", exception=e) + + +def _generate_and_run_server(config_file: str): + """Generate MCP server code and execute it.""" + import tempfile + import subprocess + import os + from pathlib import Path + + # Load and validate configuration + parser = YMLParser() + config = parser.load_from_file(config_file) + + # Generate server code + generator = FastMCPGenerator() + server_code = generator._generate_server_code(config) + + # Create a temporary file for the server + with tempfile.NamedTemporaryFile(mode='w', suffix='.py', delete=False) as f: + f.write(server_code) + temp_server_file = f.name + + try: + # Execute the generated server + print(f"🐍 Executing generated MCP server...") + subprocess.run([sys.executable, temp_server_file], check=True) + finally: + # Clean up temporary file + try: + os.unlink(temp_server_file) + except OSError: + pass # File might already be deleted + + def main(): """Main CLI entry point using Fire.""" fire.Fire({ @@ -546,5 +635,6 @@ def main(): 'new': new, 'add-tool': add_tool, 'add-resource': add_resource, - 'add-prompt': add_prompt + 'add-prompt': add_prompt, + 'run': run }) \ No newline at end of file diff --git a/shellmcp/configs/__init__.py b/shellmcp/configs/__init__.py new file mode 100644 index 0000000..b7db786 --- /dev/null +++ b/shellmcp/configs/__init__.py @@ -0,0 +1 @@ +"""Pre-configured MCP server configurations.""" \ No newline at end of file diff --git a/shellmcp/configs/basics.yml b/shellmcp/configs/basics.yml new file mode 100644 index 0000000..d50979a --- /dev/null +++ b/shellmcp/configs/basics.yml @@ -0,0 +1,218 @@ +server: + name: "basics" + desc: "Basic shell operations for file system, process management, and system information" + version: "1.0.0" + +tools: + list_files: + cmd: "{% if recursive %}find {{ path }} -type f{% else %}ls -la {{ path }}{% endif %}" + desc: "List files and directories" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + - name: recursive + help: "Include subdirectories recursively" + type: boolean + default: false + + find_files: + cmd: "find {{ path }} -name '{{ pattern }}' -type f" + desc: "Find files matching a pattern" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + - name: pattern + help: "Search pattern (supports glob patterns)" + type: string + + file_info: + cmd: "file {{ path }}" + desc: "Get detailed file information" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + + file_size: + cmd: "du -h {{ path }}" + desc: "Get file or directory size" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + + create_directory: + cmd: "mkdir -p {{ path }}" + desc: "Create a directory (including parent directories)" + args: + - name: path + help: "Directory path to create" + type: string + + remove_file: + cmd: "rm {{ path }}" + desc: "Remove a file" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + + remove_directory: + cmd: "rm -rf {{ path }}" + desc: "Remove a directory and its contents" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + + copy_file: + cmd: "cp {{ source }} {{ destination }}" + desc: "Copy a file" + args: + - name: source + help: "Source file path" + type: string + - name: destination + help: "Destination file path" + type: string + + move_file: + cmd: "mv {{ source }} {{ destination }}" + desc: "Move or rename a file" + args: + - name: source + help: "Source file path" + type: string + - name: destination + help: "Destination file path" + type: string + + read_file: + cmd: "cat {{ path }}" + desc: "Read file contents" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + + write_file: + cmd: "echo '{{ content }}' > {{ path }}" + desc: "Write content to a file" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + - name: content + help: "Content to write to the file" + type: string + + append_file: + cmd: "echo '{{ content }}' >> {{ path }}" + desc: "Append content to a file" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + - name: content + help: "Content to append to the file" + type: string + + grep_text: + cmd: "grep -r '{{ pattern }}' {{ path }}" + desc: "Search for text in files" + args: + - name: pattern + help: "Text pattern to search for" + type: string + - name: path + help: "Directory or file path" + type: string + default: "." + + process_list: + cmd: "ps aux | head -{{ count }}" + desc: "List running processes" + args: + - name: count + help: "Number of items to show" + type: number + default: 10 + + process_kill: + cmd: "kill {{ pid }}" + desc: "Kill a process by PID" + args: + - name: pid + help: "Process ID to kill" + type: number + + system_info: + cmd: "uname -a && uptime && df -h" + desc: "Get system information (OS, uptime, disk usage)" + + memory_info: + cmd: "free -h" + desc: "Get memory usage information" + + network_info: + cmd: "ifconfig || ip addr show" + desc: "Get network interface information" + + current_directory: + cmd: "pwd" + desc: "Get current working directory" + + change_directory: + cmd: "cd {{ path }} && pwd" + desc: "Change to a directory and show the new path" + args: + - name: path + help: "Directory or file path" + type: string + default: "." + + environment_vars: + cmd: "env | sort" + desc: "List all environment variables" + + command_history: + cmd: "history | tail -{{ count }}" + desc: "Show recent command history" + args: + - name: count + help: "Number of items to show" + type: number + default: 10 + +resources: + system_status: + uri: "system://status" + name: "System Status" + description: "Current system status and information" + mime_type: "text/plain" + cmd: "uname -a && echo '---' && uptime && echo '---' && df -h && echo '---' && free -h" + + current_directory_contents: + uri: "file://current-directory" + name: "Current Directory Contents" + description: "Contents of the current working directory" + mime_type: "text/plain" + cmd: "pwd && echo '---' && ls -la" + + environment: + uri: "env://variables" + name: "Environment Variables" + description: "Current environment variables" + mime_type: "text/plain" + cmd: "env | sort"