diff --git a/README.md b/README.md index d6b5a2c..16bc780 100644 --- a/README.md +++ b/README.md @@ -145,9 +145,40 @@ Generate a FastMCP server from YAML configuration. shellmcp generate my-server.yml --output-dir ./output --verbose ``` +### `shellmcp mcp-config` +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 + +ShellMCP can generate MCP server configuration JSON: + +```bash +# 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-config my_tools.yml +# Copy the JSON to your MCP client configuration! +``` + +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 [MCP Integration Guide](docs/mcp-integration.md) for detailed documentation. + ## Documentation - [YAML Specification](docs/yml-specification.md) +- [MCP Integration](docs/mcp-integration.md) ## License diff --git a/docs/mcp-integration.md b/docs/mcp-integration.md new file mode 100644 index 0000000..ae2eb42 --- /dev/null +++ b/docs/mcp-integration.md @@ -0,0 +1,146 @@ +# MCP Integration + +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" + + # Add some tools + shellmcp add-tool my_tools.yml + + # Generate the server + shellmcp generate my_tools.yml + ``` + +2. **Generate MCP configuration:** + ```bash + # Output JSON to stdout + shellmcp mcp-config my_tools.yml + + # Or save to file + shellmcp mcp-config my_tools.yml --output-file mcp.json + ``` + +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-config my_tools.yml + +# Generate JSON and save to file +shellmcp mcp-config my_tools.yml --output-file mcp.json + +# Use specific server path +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 +``` + + +## MCP Configuration Locations + +MCP clients typically look for configuration in these locations: + +- **Global**: `~/.config/mcp/config.json` +- **Local**: `./mcp.json` +- **User Config**: `~/.mcp/config.json` + +## Manual Integration + +1. **Generate the configuration:** + ```bash + shellmcp mcp-config my_tools.yml --output-file mcp_config.json + ``` + +2. **Copy to your MCP client config:** + ```bash + # For local project + cp mcp_config.json ./mcp.json + + # Or merge with existing config + # (manually edit your existing config to add the new server) + ``` + +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 `--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 +``` + +This generates: + +```json +{ + "mcpServers": { + "my-tools": { + "command": "python3", + "args": ["--no-confirm-dangerous", "/path/to/my_tools_server.py"], + "env": { + "PYTHONPATH": "/path/to/server/directory" + } + } + } +} +``` + +**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`: + +```json +{ + "mcpServers": { + "{{ server_name }}": { + "command": "{{ python_executable }}", + "args": [{% if allow_auto_confirm %}"--no-confirm-dangerous", {% endif %}"{{ server_path }}"], + "env": { + "PYTHONPATH": "{{ server_dir }}" + } + } + } +} +``` + +You can modify this template to customize the generated configuration format. + +## 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. Use `--server-path` to specify the exact path + +### JSON Format Issues + +If the generated JSON is invalid: + +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 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 MCP integration \ No newline at end of file diff --git a/shellmcp/cli.py b/shellmcp/cli.py index 96249c5..7dd83ae 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 .mcp_config import generate_mcp_config def _handle_error(error_msg: str, verbose: bool = False, exception: Exception = None) -> int: @@ -536,6 +537,40 @@ 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_config(config_file: str, server_path: str = None, python_executable: str = "python3", + output_file: str = None, allow_auto_confirm: bool = False) -> int: + """ + Generate MCP server configuration JSON. + + Args: + config_file: Path to the 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) + allow_auto_confirm: Enable auto-trusting for tools (default: False) + + 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") + + result = generate_mcp_config( + config_file, server_path, python_executable, output_file, allow_auto_confirm + ) + + if output_file: + print(result) + else: + print(result) + + return 0 + + except Exception as e: + return _handle_error(f"Error generating MCP config: {e}", exception=e) + + def main(): """Main CLI entry point using Fire.""" fire.Fire({ @@ -544,5 +579,6 @@ def main(): 'new': new, 'add-tool': add_tool, 'add-resource': add_resource, - 'add-prompt': add_prompt + 'add-prompt': add_prompt, + 'mcp-config': mcp_config }) \ No newline at end of file diff --git a/shellmcp/mcp_config.py b/shellmcp/mcp_config.py new file mode 100644 index 0000000..2b7d345 --- /dev/null +++ b/shellmcp/mcp_config.py @@ -0,0 +1,79 @@ +"""Generate MCP server configuration JSON.""" + +import json +from pathlib import Path + +from jinja2 import Environment, FileSystemLoader + +from .parser import YMLParser + + +def generate_mcp_config(yml_file: str, server_path: str = None, + python_executable: str = "python3", output_file: str = None, + allow_auto_confirm: bool = False) -> 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) + allow_auto_confirm: Enable auto-trusting for tools (default: False) + + 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, + allow_auto_confirm=allow_auto_confirm + ) + + # 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/templates/mcp_config.json.j2 b/shellmcp/templates/mcp_config.json.j2 new file mode 100644 index 0000000..3e2322c --- /dev/null +++ b/shellmcp/templates/mcp_config.json.j2 @@ -0,0 +1,11 @@ +{ + "mcpServers": { + "{{ server_name }}": { + "command": "{{ python_executable }}", + "args": [{% if allow_auto_confirm %}"--no-confirm-dangerous", {% endif %}"{{ server_path }}"], + "env": { + "PYTHONPATH": "{{ server_dir }}" + } + } + } +} \ No newline at end of file