Skip to main content
Glama

skills_install

Download and install skills to your local environment from the Skills MCP registry, enabling AI agents to add new capabilities.

Instructions

Download and install a skill to the local environment.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
nameYesExact name of the skill
forceNoOverwrite if exists

Implementation Reference

  • The main tool handler for 'skills_install', including input schema via Pydantic Fields and @mcp.tool() registration decorator. Delegates execution to local.install_skill.
    @mcp.tool() def skills_install( name: str = Field(description="Exact name of the skill"), force: bool = Field(default=False, description="Overwrite if exists") ) -> str: """Download and install a skill to the local environment.""" try: return local.install_skill(name, force) except Exception as e: return f"Installation failed: {str(e)}"
  • Core helper function implementing the skill installation: checks if exists, downloads ZIP from registry API, performs security checks, handles nested directories during extraction, verifies SKILL.md, with rollback on failure.
    def install_skill(name: str, force: bool = False) -> str: target_dir = config.root_dir / name if target_dir.exists(): if not force: return f"Skill '{name}' is already installed at {target_dir}." else: shutil.rmtree(target_dir) # Download logic url = f"{client.base_url}/download/{name}" try: with httpx.stream("GET", url, headers=client.headers, timeout=30.0) as resp: if resp.status_code == 404: raise RuntimeError(f"Skill '{name}' not found in registry.") resp.raise_for_status() # Download full content to memory (assuming zip files are small < 50MB) # For larger files, we should use a temporary file. data = io.BytesIO() for chunk in resp.iter_bytes(): data.write(chunk) with zipfile.ZipFile(data) as zf: # Security Check: Prevent Zip Slip for member in zf.namelist(): if ".." in member or member.startswith("/"): raise RuntimeError("Malicious zip file detected.") # 1. 检查是否存在顶层目录嵌套 # 改进后的判定逻辑:只要 SKILL.md 是在子目录里,就认为是嵌套的 skill_md_path = next((f for f in zf.namelist() if f.endswith("SKILL.md")), None) if skill_md_path and "/" in skill_md_path: # 例如 "docx/SKILL.md" prefix = skill_md_path.split("/SKILL.md")[0] + "/" is_nested = True else: prefix = "" is_nested = False target_dir.mkdir(parents=True, exist_ok=True) if is_nested: # 智能解压:去掉第一层目录 for member in zf.infolist(): if member.filename == prefix: continue # 跳过顶层目录本身 # 去掉前缀 new_name = member.filename[len(prefix):] if not new_name: continue target_path = target_dir / new_name if member.is_dir(): target_path.mkdir(parents=True, exist_ok=True) else: target_path.parent.mkdir(parents=True, exist_ok=True) with zf.open(member, "r") as source, open(target_path, "wb") as target: shutil.copyfileobj(source, target) else: # 直接解压 zf.extractall(target_dir) # 最后的完整性检查 if not (target_dir / "SKILL.md").exists(): # 回滚 shutil.rmtree(target_dir) raise RuntimeError("Invalid skill package: missing SKILL.md after extraction.") except httpx.HTTPStatusError as e: raise RuntimeError(f"Download failed: {e.response.text}") except Exception as e: # Cleanup partial install if target_dir.exists(): shutil.rmtree(target_dir) raise e return f"Successfully installed '{name}' to {target_dir}."

Latest Blog Posts

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/leezhuuuuu/skills-mcp'

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