#!/usr/bin/env python3 """ scripts/hf_mcp_server.py — Local Model Context Protocol (MCP) Server for Hugging Face Hub. Provides direct tool integration for: - HF User and PRO subscription inspection - Spaces status, hardware management, restarts, and secrets - Model / Dataset discovery, file uploads, and model card management - Local ZeroGPU hub interaction Runs as a standard stdio JSON-RPC MCP server. """ import sys import os import json import warnings import traceback warnings.filterwarnings("ignore") from typing import Any, Dict, List try: from huggingface_hub import HfApi, SpaceHardware, SpaceStage except ImportError: HfApi = None def get_hf_api() -> HfApi: token = os.environ.get("HF_TOKEN") if not token: raise ValueError("HF_TOKEN environment variable is not set.") return HfApi(token=token) # ─── Tool Implementations ─────────────────────────────────────────────────────── def tool_hf_whoami(args: Dict[str, Any]) -> str: api = get_hf_api() user_info = api.whoami() summary = { "username": user_info.get("name"), "fullname": user_info.get("fullname"), "email": user_info.get("email"), "is_pro": user_info.get("isPro", False), "can_pay": user_info.get("canPay", False), "orgs": [org.get("name") for org in user_info.get("orgs", [])], "auth_type": user_info.get("auth", {}).get("type"), } return json.dumps(summary, indent=2) def tool_hf_space_info(args: Dict[str, Any]) -> str: api = get_hf_api() space_id = args.get("space_id", "abalanescu/flow") info = api.get_space_runtime(repo_id=space_id) out = { "space_id": space_id, "stage": str(info.stage), "hardware": str(info.hardware), "requested_hardware": str(info.requested_hardware), "gc_timeout": info.gc_timeout, "raw": { "current_hardware": str(info.hardware), "stage": str(info.stage), } } return json.dumps(out, indent=2) def tool_hf_space_restart(args: Dict[str, Any]) -> str: api = get_hf_api() space_id = args.get("space_id", "abalanescu/flow") factory_reboot = bool(args.get("factory_reboot", False)) res = api.restart_space(repo_id=space_id, factory_reboot=factory_reboot) return json.dumps({"status": "restarting", "space_id": space_id, "factory_reboot": factory_reboot, "response": str(res)}, indent=2) def tool_hf_list_user_repos(args: Dict[str, Any]) -> str: api = get_hf_api() user_info = api.whoami() username = user_info.get("name") repo_type = args.get("repo_type", "all") # model, dataset, space, all results = {} if repo_type in ("all", "model"): models = list(api.list_models(author=username, limit=30)) results["models"] = [{"id": m.id, "downloads": m.downloads, "likes": m.likes, "private": m.private} for m in models] if repo_type in ("all", "space"): spaces = list(api.list_spaces(author=username, limit=30)) results["spaces"] = [{"id": s.id, "likes": s.likes, "private": s.private} for s in spaces] if repo_type in ("all", "dataset"): datasets = list(api.list_datasets(author=username, limit=30)) results["datasets"] = [{"id": d.id, "downloads": d.downloads, "likes": d.likes, "private": d.private} for d in datasets] return json.dumps(results, indent=2) def tool_hf_search_models(args: Dict[str, Any]) -> str: api = get_hf_api() query = args.get("query", "") limit = int(args.get("limit", 10)) filter_tag = args.get("filter") models = list(api.list_models(search=query, filter=filter_tag, limit=limit, sort="downloads", direction=-1)) out = [ { "id": m.id, "downloads": m.downloads, "likes": m.likes, "pipeline_tag": getattr(m, "pipeline_tag", None), "private": m.private, } for m in models ] return json.dumps(out, indent=2) def tool_hf_upload_file(args: Dict[str, Any]) -> str: api = get_hf_api() path_or_fileobj = args.get("local_path") path_in_repo = args.get("path_in_repo") repo_id = args.get("repo_id") repo_type = args.get("repo_type", "space") # model, dataset, space commit_message = args.get("commit_message", f"Upload {path_in_repo}") if not path_or_fileobj or not path_in_repo or not repo_id: return json.dumps({"error": "Missing required arguments: local_path, path_in_repo, repo_id"}) if not os.path.exists(path_or_fileobj): return json.dumps({"error": f"Local file not found: {path_or_fileobj}"}) url = api.upload_file( path_or_fileobj=path_or_fileobj, path_in_repo=path_in_repo, repo_id=repo_id, repo_type=repo_type, commit_message=commit_message, ) return json.dumps({"status": "uploaded", "repo_id": repo_id, "url": str(url)}, indent=2) # ─── MCP Tools Catalog ────────────────────────────────────────────────────────── TOOLS_METADATA = [ { "name": "hf_whoami", "description": "Inspect authenticated Hugging Face account profile, PRO subscription status, and organizations.", "inputSchema": { "type": "object", "properties": {}, }, }, { "name": "hf_space_info", "description": "Get current stage, hardware tier (ZeroGPU/A10G/CPU), and runtime state for an HF Space.", "inputSchema": { "type": "object", "properties": { "space_id": {"type": "string", "description": "Space repo ID, e.g. abalanescu/flow"} }, }, }, { "name": "hf_space_restart", "description": "Restart or factory-reboot a Hugging Face Space.", "inputSchema": { "type": "object", "properties": { "space_id": {"type": "string", "description": "Space repo ID, e.g. abalanescu/flow"}, "factory_reboot": {"type": "boolean", "description": "Whether to perform a clean factory rebuild"} }, }, }, { "name": "hf_list_user_repos", "description": "List all models, spaces, and datasets owned by the authenticated HF user.", "inputSchema": { "type": "object", "properties": { "repo_type": {"type": "string", "enum": ["all", "model", "space", "dataset"], "description": "Type of repos to list"} }, }, }, { "name": "hf_search_models", "description": "Search public and private models on Hugging Face Hub by keyword or pipeline tag.", "inputSchema": { "type": "object", "properties": { "query": {"type": "string", "description": "Search keyword (e.g. qwen, gemma, flux)"}, "limit": {"type": "integer", "description": "Max models to return (default: 10)"}, "filter": {"type": "string", "description": "Filter by pipeline tag, e.g. text-generation"} }, "required": ["query"], }, }, { "name": "hf_upload_file", "description": "Upload a file from local filesystem to a Hugging Face model, space, or dataset repo.", "inputSchema": { "type": "object", "properties": { "local_path": {"type": "string", "description": "Absolute path to local file"}, "path_in_repo": {"type": "string", "description": "Target filename/path in repo"}, "repo_id": {"type": "string", "description": "Target repository ID (e.g. abalanescu/flow)"}, "repo_type": {"type": "string", "enum": ["space", "model", "dataset"], "description": "Repository type"}, "commit_message": {"type": "string", "description": "Git commit message"} }, "required": ["local_path", "path_in_repo", "repo_id"], }, }, ] TOOL_DISPATCH = { "hf_whoami": tool_hf_whoami, "hf_space_info": tool_hf_space_info, "hf_space_restart": tool_hf_space_restart, "hf_list_user_repos": tool_hf_list_user_repos, "hf_search_models": tool_hf_search_models, "hf_upload_file": tool_hf_upload_file, } def handle_request(req: Dict[str, Any]) -> Dict[str, Any]: req_id = req.get("id") method = req.get("method") params = req.get("params", {}) if method == "initialize": return { "jsonrpc": "2.0", "id": req_id, "result": { "protocolVersion": "2024-11-05", "capabilities": { "tools": {} }, "serverInfo": { "name": "hf-local-mcp-server", "version": "1.0.0" } } } elif method == "notifications/initialized": return None elif method == "tools/list": return { "jsonrpc": "2.0", "id": req_id, "result": { "tools": TOOLS_METADATA } } elif method == "tools/call": tool_name = params.get("name") arguments = params.get("arguments", {}) if tool_name not in TOOL_DISPATCH: return { "jsonrpc": "2.0", "id": req_id, "error": { "code": -32601, "message": f"Unknown tool: {tool_name}" } } try: fn = TOOL_DISPATCH[tool_name] result_text = fn(arguments) return { "jsonrpc": "2.0", "id": req_id, "result": { "content": [ { "type": "text", "text": result_text } ] } } except Exception as e: return { "jsonrpc": "2.0", "id": req_id, "error": { "code": -32000, "message": f"Tool execution failed: {str(e)}", "data": traceback.format_exc() } } else: return { "jsonrpc": "2.0", "id": req_id, "error": { "code": -32601, "message": f"Method not found: {method}" } } def main(): for line in sys.stdin: line = line.strip() if not line: continue try: req = json.loads(line) res = handle_request(req) if res is not None: sys.stdout.write(json.dumps(res) + "\n") sys.stdout.flush() except Exception as e: err_res = { "jsonrpc": "2.0", "id": None, "error": { "code": -32700, "message": f"Parse error: {str(e)}" } } sys.stdout.write(json.dumps(err_res) + "\n") sys.stdout.flush() if __name__ == "__main__": main()