Skip to main content
Glama

MCP Builder

by XD3an
server.py12.4 kB
#!/usr/bin/env python import json import os import platform import re import shutil import subprocess from pathlib import Path from typing import Dict, List, Optional, Union from mcp.server.fastmcp import Context, FastMCP # Create MCP server mcp = FastMCP( name="mcp-builder", version="0.1.0", description="A Python MCP server to install other MCP servers", ) def get_claude_desktop_config_path() -> Optional[str]: """Get the path to the Claude Desktop configuration file.""" home = os.path.expanduser("~") if platform.system() == "Windows": return os.path.join(os.environ.get("APPDATA", ""), "Claude", "claude_desktop_config.json") elif platform.system() == "Darwin": # macOS return os.path.join(home, "Library", "Application Support", "Claude", "claude_desktop_config.json") else: # Linux and others return os.path.join(home, ".config", "Claude", "claude_desktop_config.json") def read_config() -> Dict: """Read the Claude Desktop configuration file.""" config_path = get_claude_desktop_config_path() if not config_path or not os.path.exists(config_path): return {} try: with open(config_path, "r", encoding="utf-8") as f: return json.load(f) except json.JSONDecodeError: return {} def write_config(config: Dict) -> None: """Write to the Claude Desktop configuration file.""" config_path = get_claude_desktop_config_path() if not config_path: raise ValueError("Could not find Claude Desktop config file") config_dir = os.path.dirname(config_path) os.makedirs(config_dir, exist_ok=True) with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) def parse_env_vars(env_vars: Optional[List[str]]) -> Optional[Dict[str, str]]: """Parse environment variables from a list of KEY=VALUE strings.""" if not env_vars: return None env_obj = {} for env_var in env_vars: if "=" in env_var: key, value = env_var.split("=", 1) env_obj[key] = value return env_obj if env_obj else None def check_command_exists(command: str) -> bool: """Check if a command exists in the system PATH.""" return shutil.which(command) is not None def run_command( command: List[str], cwd: Optional[Path] = None, env: Optional[Dict[str, str]] = None ) -> tuple[bool, str]: """Run a command and return its success status and output.""" try: env_dict = os.environ.copy() if env: env_dict.update(env) result = subprocess.run( command, cwd=cwd, env=env_dict, capture_output=True, text=True, check=False, ) return result.returncode == 0, result.stdout + result.stderr except Exception as e: return False, str(e) def is_pypi_package(package_name: str) -> bool: """Check if a package exists on PyPI.""" success, _ = run_command(["pip", "search", package_name]) return success def is_npm_package(package_name: str) -> bool: """Check if a package exists on npm.""" success, _ = run_command(["npm", "view", package_name]) return success def install_to_claude_desktop( server_name: str, command: str, args: List[str], env: Optional[List[str]] = None, cwd: Optional[str] = None, ) -> None: """ Install an MCP server to Claude Desktop. Args: server_name: The name of the MCP server command: The command to run the MCP server args: The arguments to pass to the command env: The environment variables to set, delimited by = cwd: The working directory for the command """ # Normalize server name to be a valid identifier # For npm packages, make sure we use a simple name without @ or / if server_name.startswith("@"): # For @scope/package, use just "package" as the server name if "/" in server_name: server_name = server_name.split("/")[1] # Remove any invalid characters from server name server_name = re.sub(r'[^a-zA-Z0-9_-]', '-', server_name) # Get the Claude Desktop config file path config_path = get_claude_desktop_config_path() if not config_path: raise ValueError("Could not find Claude Desktop config file") # Read the existing config try: with open(config_path, "r", encoding="utf-8") as f: config = json.load(f) except (FileNotFoundError, json.JSONDecodeError): config = {} # Initialize mcpServers if it doesn't exist if "mcpServers" not in config: config["mcpServers"] = {} # Prepare the server config server_config = { "command": command, "args": args, } # Add environment variables if provided if env and len(env) > 0: server_config["env"] = parse_env_vars(env) # Add working directory if provided if cwd: server_config["cwd"] = cwd # Add the server to the config config["mcpServers"][server_name] = server_config # Write the config back to the file with open(config_path, "w", encoding="utf-8") as f: json.dump(config, f, indent=2) @mcp.tool() def install_repo_mcp_server( name: str, args: Optional[List[str]] = None, env: Optional[List[str]] = None, ) -> str: """ Install an MCP server via pip or npm. Args: name: The package name of the MCP server args: The arguments to pass along env: The environment variables to set, delimited by = """ if args is None: args = [] # Check if Node.js is installed (for npm packages) has_node = check_command_exists("node") has_npm = check_command_exists("npm") has_npx = check_command_exists("npx") has_pip = check_command_exists("pip") has_python = check_command_exists("python") if not has_node and not has_python: return "Neither Node.js nor Python is installed. Please install one of them." # Determine if this is likely an npm package (starts with @ or doesn't have dots) is_likely_npm_package = name.startswith('@') or '.' not in name # Handle npm packages if has_npm and has_npx and is_likely_npm_package: # Extract server name from package name (handle scoped packages) if name.startswith("@") and "/" in name: # For @scope/package, use just "package" as the server name server_name = name.split("/")[1] else: server_name = name # Install to Claude Desktop using npx directly install_to_claude_desktop( server_name, "npx", [name] + args, env, ) return f"Successfully installed MCP server '{server_name}' via npx! Please tell the user to restart the application." # Handle Python packages (likely has dots in the name) if has_pip and has_python and '.' in name: # For Python packages, use the package name directly server_name = name # Install to Claude Desktop install_to_claude_desktop( server_name, "python", ["-m", name] + args, env, ) return f"Successfully installed MCP server '{server_name}' via Python! Please tell the user to restart the application." # If we can't determine the type, try npm first if available, then Python if has_npm and has_npx: # Use the last part of the name as the server name if "/" in name: server_name = name.split("/")[-1] else: server_name = name install_to_claude_desktop( server_name, "npx", [name] + args, env, ) return f"Successfully installed MCP server '{server_name}' via npx! Please tell the user to restart the application." elif has_pip and has_python: server_name = name.split(".")[-1] if "." in name else name install_to_claude_desktop( server_name, "python", ["-m", name] + args, env, ) return f"Successfully installed MCP server '{server_name}' via Python! Please tell the user to restart the application." return f"Could not determine how to install '{name}'" @mcp.tool() def install_local_mcp_server( path: str, args: Optional[List[str]] = None, env: Optional[List[str]] = None, ) -> str: """ Install an MCP server from a local directory. Args: path: The path to the MCP server code cloned on your computer args: The arguments to pass along env: The environment variables to set, delimited by = """ if args is None: args = [] if not os.path.exists(path): return f"Path '{path}' does not exist." # Check if it's a Node.js or Python project has_package_json = os.path.exists(os.path.join(path, "package.json")) has_pyproject_toml = os.path.exists(os.path.join(path, "pyproject.toml")) has_setup_py = os.path.exists(os.path.join(path, "setup.py")) # Determine server name from directory name server_name = os.path.basename(path) # Check if Node.js is installed (for npm packages) has_node = check_command_exists("node") has_npm = check_command_exists("npm") has_pip = check_command_exists("pip") has_python = check_command_exists("python") # Handle Node.js projects if has_package_json and has_node and has_npm: # For Node.js projects, use node directly to run the local script # Find the main entry point from package.json try: with open(os.path.join(path, "package.json"), "r") as f: package_json = json.load(f) # Try to find the main entry point main_file = package_json.get("main", "index.js") # Install to Claude Desktop install_to_claude_desktop( server_name, "node", [os.path.join(path, main_file)] + args, env, ) return f"Successfully installed local Node.js MCP server '{server_name}'! Please tell the user to restart the application." except Exception as e: return f"Error installing Node.js MCP server: {str(e)}" # Handle Python projects elif (has_pyproject_toml or has_setup_py) and has_python: # For Python projects, use python -m to run the module # Try to determine the module name from directory structure module_name = server_name.replace("-", "_") # Check if there's a directory with the same name if os.path.isdir(os.path.join(path, module_name)): # Install to Claude Desktop install_to_claude_desktop( server_name, "python", ["-m", module_name] + args, env, cwd=path, ) return f"Successfully installed local Python MCP server '{server_name}'! Please tell the user to restart the application." else: # Try to find any Python files in the root directory py_files = [f for f in os.listdir(path) if f.endswith(".py") and f != "setup.py"] if py_files: main_file = py_files[0].replace(".py", "") # Install to Claude Desktop install_to_claude_desktop( server_name, "python", [os.path.join(path, main_file + ".py")] + args, env, ) return f"Successfully installed local Python MCP server '{server_name}'! Please tell the user to restart the application." return f"Could not determine how to install MCP server from '{path}'. Make sure it's a valid Node.js or Python project." def main(): """Run the MCP Builder server.""" mcp.run() if __name__ == "__main__": main()

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/XD3an/mcp-builder'

If you have feedback or need assistance with the MCP directory API, please join our Discord server