FastMCP

"""Claude app integration utilities.""" import json import sys from pathlib import Path from mcp.server.fastmcp.utilities.logging import get_logger logger = get_logger(__name__) MCP_PACKAGE = "mcp[cli]" def get_claude_config_path() -> Path | None: """Get the Claude config directory based on platform.""" if sys.platform == "win32": path = Path(Path.home(), "AppData", "Roaming", "Claude") elif sys.platform == "darwin": path = Path(Path.home(), "Library", "Application Support", "Claude") else: return None if path.exists(): return path return None def update_claude_config( file_spec: str, server_name: str, *, with_editable: Path | None = None, with_packages: list[str] | None = None, env_vars: dict[str, str] | None = None, ) -> bool: """Add or update a FastMCP server in Claude's configuration. Args: file_spec: Path to the server file, optionally with :object suffix server_name: Name for the server in Claude's config with_editable: Optional directory to install in editable mode with_packages: Optional list of additional packages to install env_vars: Optional dictionary of environment variables. These are merged with any existing variables, with new values taking precedence. Raises: RuntimeError: If Claude Desktop's config directory is not found, indicating Claude Desktop may not be installed or properly set up. """ config_dir = get_claude_config_path() if not config_dir: raise RuntimeError( "Claude Desktop config directory not found. Please ensure Claude Desktop" " is installed and has been run at least once to initialize its config." ) config_file = config_dir / "claude_desktop_config.json" if not config_file.exists(): try: config_file.write_text("{}") except Exception as e: logger.error( "Failed to create Claude config file", extra={ "error": str(e), "config_file": str(config_file), }, ) return False try: config = json.loads(config_file.read_text()) if "mcpServers" not in config: config["mcpServers"] = {} # Always preserve existing env vars and merge with new ones if ( server_name in config["mcpServers"] and "env" in config["mcpServers"][server_name] ): existing_env = config["mcpServers"][server_name]["env"] if env_vars: # New vars take precedence over existing ones env_vars = {**existing_env, **env_vars} else: env_vars = existing_env # Build uv run command args = ["run"] # Collect all packages in a set to deduplicate packages = {MCP_PACKAGE} if with_packages: packages.update(pkg for pkg in with_packages if pkg) # Add all packages with --with for pkg in sorted(packages): args.extend(["--with", pkg]) if with_editable: args.extend(["--with-editable", str(with_editable)]) # Convert file path to absolute before adding to command # Split off any :object suffix first if ":" in file_spec: file_path, server_object = file_spec.rsplit(":", 1) file_spec = f"{Path(file_path).resolve()}:{server_object}" else: file_spec = str(Path(file_spec).resolve()) # Add fastmcp run command args.extend(["mcp", "run", file_spec]) server_config = { "command": "uv", "args": args, } # Add environment variables if specified if env_vars: server_config["env"] = env_vars config["mcpServers"][server_name] = server_config config_file.write_text(json.dumps(config, indent=2)) logger.info( f"Added server '{server_name}' to Claude config", extra={"config_file": str(config_file)}, ) return True except Exception as e: logger.error( "Failed to update Claude config", extra={ "error": str(e), "config_file": str(config_file), }, ) return False