evalstate HF Staff commited on
Commit
a60a605
·
verified ·
1 Parent(s): 7833b5a

Deploy HF Hub Community agent with OAuth token passthrough

Browse files
Files changed (4) hide show
  1. Dockerfile +33 -0
  2. README.md +65 -6
  3. hf_api_tool.py +224 -0
  4. hf_hub_community.md +393 -0
Dockerfile ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.13-slim
2
+
3
+ # Install system dependencies required by fast-agent and HF Spaces
4
+ RUN apt-get update && \
5
+ apt-get install -y \
6
+ bash \
7
+ git git-lfs \
8
+ wget curl procps \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ # Install uv for fast, reliable package management
12
+ COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
13
+
14
+ # Set working directory
15
+ WORKDIR /app
16
+
17
+ # Install fast-agent-mcp from PyPI
18
+ RUN uv pip install --system --no-cache fast-agent-mcp
19
+
20
+ # Copy all files from the Space repository to /app
21
+ COPY --link ./ /app
22
+
23
+ # Ensure /app is owned by uid 1000 (required for HF Spaces)
24
+ RUN chown -R 1000:1000 /app
25
+
26
+ # Switch to non-root user
27
+ USER 1000
28
+
29
+ # Expose port 7860 (HF Spaces default)
30
+ EXPOSE 7860
31
+
32
+ # Run fast-agent serve with request-scoped instances for token passthrough
33
+ CMD ["fast-agent", "serve", "--card", "hf_hub_community.md", "--transport", "http", "--instance-scope", "request", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,10 +1,69 @@
1
  ---
2
- title: Hf Hub Community
3
- emoji: 🔥
4
- colorFrom: purple
5
- colorTo: gray
6
  sdk: docker
7
- pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: HF Hub Community Agent
3
+ emoji: 🤗
4
+ colorFrom: yellow
5
+ colorTo: red
6
  sdk: docker
7
+ app_port: 7860
8
+ license: apache-2.0
9
+ short_description: Query HF users, orgs, discussions, PRs, and more
10
  ---
11
 
12
+ # HF Hub Community Agent
13
+
14
+ This Space runs a [fast-agent](https://fast-agent.ai/) agent specialized in querying Hugging Face Hub community features.
15
+
16
+ ## Capabilities
17
+
18
+ This agent can help you with:
19
+
20
+ - **User & Organization Data**: Get user profiles, overview info, followers, and following lists
21
+ - **Discussions & Pull Requests**: Browse, create, and manage discussions and PRs on repos
22
+ - **Access Requests**: Manage access requests for gated repositories
23
+ - **Collections**: List, view, and manage HF collections
24
+ - **Likes & Interactions**: Query liked repositories and user activity
25
+
26
+ ## Configuration
27
+
28
+ The agent uses the HuggingFace API with:
29
+ - `hf_api_tool.py`: Secure API tool with endpoint allowlist
30
+ - `hf_hub_community.md`: Agent card with detailed method documentation
31
+
32
+ ## OAuth Token Passthrough
33
+
34
+ This Space is configured for **user-pays** inference:
35
+ - Users provide their own HuggingFace token via `Authorization: Bearer <token>` header
36
+ - Inference costs are billed to the user's HF account
37
+ - Supports HF Inference API models
38
+
39
+ ## Environment Variables
40
+
41
+ Configure in Space Settings:
42
+
43
+ - `HF_TOKEN`: Fallback token for unauthenticated requests (optional)
44
+ - `HF_ENDPOINT`: Override HF endpoint (default: https://huggingface.co)
45
+ - `HF_MAX_RESULTS`: Max results for list responses (default: 20)
46
+
47
+ ## Usage
48
+
49
+ This Space exposes a fast-agent MCP server. You can interact with it using:
50
+
51
+ 1. **MCP Clients**: Connect using the Space URL with your HF token
52
+ 2. **HTTP API**: Send requests with `Authorization: Bearer <your-hf-token>` header
53
+ 3. **Claude Desktop**: Configure as an MCP server
54
+
55
+ ## Security
56
+
57
+ The `hf_api_tool.py` includes:
58
+ - Endpoint allowlist for security
59
+ - Token validation and path traversal protection
60
+ - Request-scoped token passthrough for OAuth
61
+
62
+ ## Model
63
+
64
+ Uses `gpt-oss` model (configurable via agent card frontmatter).
65
+
66
+ ## More Info
67
+
68
+ - [Fast Agent Documentation](https://fast-agent.ai/)
69
+ - [Hugging Face API Documentation](https://huggingface.co/docs/huggingface_hub)
hf_api_tool.py ADDED
@@ -0,0 +1,224 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import re
6
+ from pathlib import Path
7
+ from typing import Any
8
+ from urllib.error import HTTPError, URLError
9
+ from urllib.parse import urlencode
10
+ from urllib.request import Request, urlopen
11
+
12
+ DEFAULT_MAX_RESULTS = 20
13
+ DEFAULT_TIMEOUT_SEC = 30
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # Endpoint allowlist (regex patterns)
17
+ # Only endpoints matching these patterns are permitted.
18
+ # ---------------------------------------------------------------------------
19
+ ALLOWED_ENDPOINT_PATTERNS: list[str] = [
20
+ # User data
21
+ r"^/whoami-v2$",
22
+ r"^/users/[^/]+/overview$",
23
+ r"^/users/[^/]+/likes$",
24
+ r"^/users/[^/]+/followers$",
25
+ r"^/users/[^/]+/following$",
26
+ # Organizations
27
+ r"^/organizations/[^/]+/overview$",
28
+ r"^/organizations/[^/]+/members$",
29
+ r"^/organizations/[^/]+/followers$",
30
+ # Discussions & PRs (repo_type: models, datasets, spaces)
31
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/discussions$",
32
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/discussions/\d+$",
33
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/discussions/\d+/comment$",
34
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/discussions/\d+/comment/[^/]+/edit$",
35
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/discussions/\d+/comment/[^/]+/hide$",
36
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/discussions/\d+/status$",
37
+ # Access requests (gated repos)
38
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/user-access-request/pending$",
39
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/user-access-request/accepted$",
40
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/user-access-request/rejected$",
41
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/user-access-request/handle$",
42
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/user-access-request/grant$",
43
+ # Collections
44
+ r"^/collections$",
45
+ r"^/collections/[^/]+$",
46
+ r"^/collections/[^/]+/items$",
47
+ # Auth check
48
+ r"^/(models|datasets|spaces)/[^/]+/[^/]+/auth-check$",
49
+ ]
50
+
51
+ _COMPILED_PATTERNS: list[re.Pattern[str]] = [
52
+ re.compile(p) for p in ALLOWED_ENDPOINT_PATTERNS
53
+ ]
54
+
55
+
56
+ def _is_endpoint_allowed(endpoint: str) -> bool:
57
+ """Return True if endpoint matches any allowed pattern."""
58
+ return any(pattern.match(endpoint) for pattern in _COMPILED_PATTERNS)
59
+
60
+
61
+ def _load_token() -> str | None:
62
+ # Check for request-scoped token first (when running as MCP server)
63
+ # This allows clients to pass their own HF token via Authorization header
64
+ try:
65
+ from fast_agent.mcp.auth.context import request_bearer_token
66
+
67
+ ctx_token = request_bearer_token.get()
68
+ if ctx_token:
69
+ return ctx_token
70
+ except ImportError:
71
+ # fast_agent.mcp.auth.context not available
72
+ pass
73
+
74
+ # Fall back to HF_TOKEN environment variable
75
+ token = os.getenv("HF_TOKEN")
76
+ if token:
77
+ return token
78
+
79
+ # Fall back to cached huggingface token file
80
+ token_path = Path.home() / ".cache" / "huggingface" / "token"
81
+ if token_path.exists():
82
+ token_value = token_path.read_text(encoding="utf-8").strip()
83
+ return token_value or None
84
+
85
+ return None
86
+
87
+
88
+ def _max_results_from_env() -> int:
89
+ raw = os.getenv("HF_MAX_RESULTS")
90
+ if not raw:
91
+ return DEFAULT_MAX_RESULTS
92
+ try:
93
+ value = int(raw)
94
+ except ValueError:
95
+ return DEFAULT_MAX_RESULTS
96
+ return value if value > 0 else DEFAULT_MAX_RESULTS
97
+
98
+
99
+ def _normalize_endpoint(endpoint: str) -> str:
100
+ """Normalize and validate an endpoint path.
101
+
102
+ Checks:
103
+ - Must be a relative path (not a full URL)
104
+ - Must be non-empty
105
+ - No path traversal sequences (..)
106
+ - Must match the endpoint allowlist
107
+ """
108
+ if endpoint.startswith("http://") or endpoint.startswith("https://"):
109
+ raise ValueError("Endpoint must be a path relative to /api, not a full URL.")
110
+ endpoint = endpoint.strip()
111
+ if not endpoint:
112
+ raise ValueError("Endpoint must be a non-empty string.")
113
+
114
+ # Path traversal protection
115
+ if ".." in endpoint:
116
+ raise ValueError("Path traversal sequences (..) are not allowed in endpoints.")
117
+
118
+ if not endpoint.startswith("/"):
119
+ endpoint = f"/{endpoint}"
120
+
121
+ # Allowlist validation
122
+ if not _is_endpoint_allowed(endpoint):
123
+ raise ValueError(
124
+ f"Endpoint '{endpoint}' is not in the allowed list. "
125
+ "See ALLOWED_ENDPOINT_PATTERNS for permitted endpoints."
126
+ )
127
+
128
+ return endpoint
129
+
130
+
131
+ def _normalize_params(params: dict[str, Any] | None) -> dict[str, Any]:
132
+ if not params:
133
+ return {}
134
+ normalized: dict[str, Any] = {}
135
+ for key, value in params.items():
136
+ if value is None:
137
+ continue
138
+ if isinstance(value, (list, tuple)):
139
+ normalized[key] = [str(item) for item in value]
140
+ else:
141
+ normalized[key] = str(value)
142
+ return normalized
143
+
144
+
145
+ def _build_url(endpoint: str, params: dict[str, Any] | None) -> str:
146
+ base = os.getenv("HF_ENDPOINT", "https://huggingface.co").rstrip("/")
147
+ url = f"{base}/api{_normalize_endpoint(endpoint)}"
148
+ normalized_params = _normalize_params(params)
149
+ if normalized_params:
150
+ url = f"{url}?{urlencode(normalized_params, doseq=True)}"
151
+ return url
152
+
153
+
154
+ def hf_api_request(
155
+ endpoint: str,
156
+ method: str = "GET",
157
+ params: dict[str, Any] | None = None,
158
+ json_body: dict[str, Any] | None = None,
159
+ max_results: int | None = None,
160
+ offset: int | None = None,
161
+ ) -> dict[str, Any]:
162
+ """
163
+ Call the Hugging Face Hub API (GET/POST only).
164
+
165
+ Args:
166
+ endpoint: API endpoint relative to /api (e.g. "/whoami-v2").
167
+ method: HTTP method (GET or POST).
168
+ params: Optional query parameters.
169
+ json_body: Optional JSON payload for POST requests.
170
+ max_results: Max results when response is a list (defaults to HF_MAX_RESULTS).
171
+ offset: Client-side offset when response is a list (defaults to 0).
172
+
173
+ Returns:
174
+ A dict with the response data and request metadata.
175
+ """
176
+ method_upper = method.upper()
177
+ if method_upper not in {"GET", "POST"}:
178
+ raise ValueError("Only GET and POST are allowed for hf_api_request.")
179
+
180
+ if method_upper == "GET" and json_body is not None:
181
+ raise ValueError("GET requests do not accept json_body.")
182
+
183
+ url = _build_url(endpoint, params)
184
+
185
+ headers = {
186
+ "Accept": "application/json",
187
+ }
188
+ token = _load_token()
189
+ if token:
190
+ headers["Authorization"] = f"Bearer {token}"
191
+
192
+ data = None
193
+ if method_upper == "POST":
194
+ headers["Content-Type"] = "application/json"
195
+ data = json.dumps(json_body or {}).encode("utf-8")
196
+
197
+ request = Request(url, headers=headers, data=data, method=method_upper)
198
+
199
+ try:
200
+ with urlopen(request, timeout=DEFAULT_TIMEOUT_SEC) as response:
201
+ raw = response.read()
202
+ status_code = response.status
203
+ except HTTPError as exc:
204
+ error_body = exc.read().decode("utf-8", errors="replace")
205
+ raise RuntimeError(f"HF API error {exc.code} for {url}: {error_body}") from exc
206
+ except URLError as exc:
207
+ raise RuntimeError(f"HF API request failed for {url}: {exc}") from exc
208
+
209
+ try:
210
+ payload = json.loads(raw)
211
+ except json.JSONDecodeError:
212
+ payload = raw.decode("utf-8", errors="replace")
213
+
214
+ if isinstance(payload, list):
215
+ limit = max_results if max_results is not None else _max_results_from_env()
216
+ start = max(offset or 0, 0)
217
+ end = start + max(limit, 0)
218
+ payload = payload[start:end]
219
+
220
+ return {
221
+ "url": url,
222
+ "status": status_code,
223
+ "data": payload,
224
+ }
hf_hub_community.md ADDED
@@ -0,0 +1,393 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ function_tools:
3
+ - hf_api_tool.py:hf_api_request
4
+ model: gpt-oss
5
+ description: "Query Hugging Face community features: user/org profiles, followers, repo discussions, pull requests, comments, access requests, and collections. Use for people lookups and repo collaboration—not for model/dataset search."
6
+ ---
7
+ Hugging Face Hub Methods: How to Call (User/Org Focus)
8
+ ======================================================
9
+
10
+ Scope
11
+ -----
12
+ This card summarizes the curated user/organization-related methods and how to call
13
+ them via the hf_api_request tool (no shell usage).
14
+
15
+ References:
16
+ - Curated method list (embedded below from scripts/hf_api_methods.txt)
17
+ - REST endpoints: scripts/hf_api_endpoints.txt
18
+ - Tool: hf_api_request (this card's function tool)
19
+
20
+ Prereqs
21
+ -------
22
+ - HF_TOKEN env var (or ~/.cache/huggingface/token)
23
+ - Optional: HF_ENDPOINT (default: https://huggingface.co)
24
+ - Optional: HF_MAX_RESULTS (default: 20)
25
+
26
+ Preferred: hf_api_request tool
27
+ ------------------------------
28
+ Tool call pattern:
29
+ - GET: hf_api_request(endpoint="/whoami-v2")
30
+ - GET with params: hf_api_request(endpoint="/users/{username}/likes")
31
+ - GET with local slicing: hf_api_request(endpoint="/users/{username}/likes", max_results=20, offset=20)
32
+ - POST: hf_api_request(endpoint="/.../comment", method="POST", json_body={...})
33
+
34
+ Notes:
35
+ - For repo operations, use /models, /datasets, or /spaces based on repo_type.
36
+ - Only GET/POST are supported by this tool. PATCH/DELETE are not supported.
37
+ - Avoid destructive operations unless the user explicitly confirms.
38
+ - List responses are client-sliced only; use max_results and offset to page
39
+ locally (the API still returns the full list).
40
+
41
+ USER DATA
42
+ ---------
43
+ - whoami
44
+ tool: hf_api_request(endpoint="/whoami-v2")
45
+
46
+ - activity (HTML scrape, not a public API endpoint)
47
+ tool: not available (HTML scrape is not supported by hf_api_request)
48
+
49
+ - get_user_overview
50
+ tool: hf_api_request(endpoint="/users/{username}/overview")
51
+
52
+ - list_liked_repos
53
+ tool: hf_api_request(endpoint="/users/{username}/likes")
54
+
55
+ - get_token_permission
56
+ tool: not available (use /whoami-v2 and check auth.accessToken.role)
57
+
58
+ USER NETWORK
59
+ ------------
60
+ - list_user_followers
61
+ tool: hf_api_request(endpoint="/users/{username}/followers")
62
+
63
+ - list_user_following
64
+ tool: hf_api_request(endpoint="/users/{username}/following")
65
+
66
+ ORGANIZATIONS
67
+ -------------
68
+ - get_organization_overview
69
+ tool: hf_api_request(endpoint="/organizations/{organization}/overview")
70
+
71
+ - list_organization_members
72
+ tool: hf_api_request(endpoint="/organizations/{organization}/members")
73
+
74
+ - list_organization_followers
75
+ tool: hf_api_request(endpoint="/organizations/{organization}/followers")
76
+
77
+ DISCUSSIONS & PULL REQUESTS
78
+ ---------------------------
79
+ - get_repo_discussions
80
+ tool: hf_api_request(
81
+ endpoint="/{repo_type}s/{repo_id}/discussions",
82
+ params={"type": "pr|discussion", "author": "<user>", "status": "open|closed"}
83
+ )
84
+
85
+ - get_discussion_details
86
+ tool: hf_api_request(
87
+ endpoint="/{repo_type}s/{repo_id}/discussions/{num}",
88
+ params={"diff": 1}
89
+ )
90
+
91
+ - create_discussion
92
+ tool: hf_api_request(
93
+ endpoint="/{repo_type}s/{repo_id}/discussions",
94
+ method="POST",
95
+ json_body={"title": "...", "description": "...", "pullRequest": false}
96
+ )
97
+
98
+ - create_pull_request
99
+ tool: hf_api_request(
100
+ endpoint="/{repo_type}s/{repo_id}/discussions",
101
+ method="POST",
102
+ json_body={"title": "...", "description": "...", "pullRequest": true}
103
+ )
104
+
105
+ - comment_discussion
106
+ tool: hf_api_request(
107
+ endpoint="/{repo_type}s/{repo_id}/discussions/{num}/comment",
108
+ method="POST",
109
+ json_body={"comment": "..."}
110
+ )
111
+
112
+ - edit_discussion_comment
113
+ tool: hf_api_request(
114
+ endpoint="/{repo_type}s/{repo_id}/discussions/{num}/comment/{comment_id}/edit",
115
+ method="POST",
116
+ json_body={"content": "..."}
117
+ )
118
+
119
+ - hide_discussion_comment (destructive)
120
+ tool: only with explicit confirmation:
121
+ hf_api_request(
122
+ endpoint="/{repo_type}s/{repo_id}/discussions/{num}/comment/{comment_id}/hide",
123
+ method="POST"
124
+ )
125
+
126
+ - change_discussion_status
127
+ tool: hf_api_request(
128
+ endpoint="/{repo_type}s/{repo_id}/discussions/{num}/status",
129
+ method="POST",
130
+ json_body={"status": "open|closed", "comment": "..."}
131
+ )
132
+
133
+ ACCESS REQUESTS (GATED REPOS)
134
+ -----------------------------
135
+ - list_pending_access_requests
136
+ tool: hf_api_request(endpoint="/{repo_type}s/{repo_id}/user-access-request/pending")
137
+
138
+ - list_accepted_access_requests
139
+ tool: hf_api_request(endpoint="/{repo_type}s/{repo_id}/user-access-request/accepted")
140
+
141
+ - list_rejected_access_requests
142
+ tool: hf_api_request(endpoint="/{repo_type}s/{repo_id}/user-access-request/rejected")
143
+
144
+ - accept_access_request
145
+ tool: hf_api_request(
146
+ endpoint="/{repo_type}s/{repo_id}/user-access-request/handle",
147
+ method="POST",
148
+ json_body={"user": "...", "status": "accepted"}
149
+ )
150
+
151
+ - cancel_access_request
152
+ tool: hf_api_request(
153
+ endpoint="/{repo_type}s/{repo_id}/user-access-request/handle",
154
+ method="POST",
155
+ json_body={"user": "...", "status": "pending"}
156
+ )
157
+
158
+ - reject_access_request (destructive)
159
+ tool: only with explicit confirmation:
160
+ hf_api_request(
161
+ endpoint="/{repo_type}s/{repo_id}/user-access-request/handle",
162
+ method="POST",
163
+ json_body={"user": "...", "status": "rejected", "rejectionReason": "..."}
164
+ )
165
+
166
+ - grant_access
167
+ tool: hf_api_request(
168
+ endpoint="/{repo_type}s/{repo_id}/user-access-request/grant",
169
+ method="POST",
170
+ json_body={"user": "..."}
171
+ )
172
+
173
+ USER COLLECTIONS
174
+ ----------------
175
+ - list_collections
176
+ tool: hf_api_request(endpoint="/collections", params={"owner": "<user-or-org>"})
177
+
178
+ - get_collection
179
+ tool: hf_api_request(endpoint="/collections/{slug}")
180
+
181
+ - create_collection
182
+ tool: hf_api_request(
183
+ endpoint="/collections",
184
+ method="POST",
185
+ json_body={"title": "...", "namespace": "<user-or-org>", "description": "...", "private": false}
186
+ )
187
+
188
+ - delete_collection
189
+ tool: DELETE not supported by hf_api_request
190
+
191
+ - add_collection_item
192
+ tool: hf_api_request(
193
+ endpoint="/collections/{slug}/items",
194
+ method="POST",
195
+ json_body={"item": {"id": "...", "type": "model|dataset|space|paper"}, "note": "..."}
196
+ )
197
+
198
+ - delete_collection_item
199
+ tool: DELETE not supported by hf_api_request
200
+
201
+ - update_collection_item
202
+ tool: PATCH not supported by hf_api_request
203
+
204
+ - update_collection_metadata
205
+ tool: PATCH not supported by hf_api_request
206
+
207
+ USER INTERACTIONS
208
+ -----------------
209
+ - like
210
+ tool: not available (Hub disables like API)
211
+
212
+ - unlike
213
+ tool: DELETE not supported by hf_api_request
214
+
215
+ - auth_check
216
+ tool: hf_api_request(endpoint="/{repo_type}s/{repo_id}/auth-check")
217
+
218
+ Direct REST usage example
219
+ -------------------------
220
+ hf_api_request(endpoint="/organizations/<org>/overview")
221
+
222
+ See scripts/hf_api_endpoints.txt for full endpoint details and expected request bodies.
223
+
224
+ Curated HfApi Methods: User & Organization Data, Discussions & Interactions
225
+ ===========================================================================
226
+ Note: Some methods map to PATCH/DELETE endpoints, which are not supported by hf_api_request.
227
+ Use these as reference unless the tool is extended.
228
+
229
+ 34 methods selected from 126 total HfApi methods
230
+
231
+
232
+ USER DATA (4 methods)
233
+ ================================================================================
234
+
235
+ get_user_overview(username: str, token: ...) -> User
236
+ --------------------------------------------------------------------------------
237
+ Get an overview of a user on the Hub.
238
+
239
+ list_liked_repos(user: Optional[str] = None, *, token: ...) -> UserLikes
240
+ --------------------------------------------------------------------------------
241
+ List all public repos liked by a user on huggingface.co.
242
+
243
+ whoami(token: ...) -> Dict
244
+ --------------------------------------------------------------------------------
245
+ Call HF API to know "whoami".
246
+
247
+ get_token_permission(token: ...) -> Literal['read', 'write', 'fineGrained', None]
248
+ --------------------------------------------------------------------------------
249
+ Check if a given token is valid and return its permissions.
250
+
251
+
252
+ USER NETWORK (2 methods)
253
+ ================================================================================
254
+
255
+ list_user_followers(username: str, token: ...) -> Iterable[User]
256
+ --------------------------------------------------------------------------------
257
+ Get the list of followers of a user on the Hub.
258
+
259
+ list_user_following(username: str, token: ...) -> Iterable[User]
260
+ --------------------------------------------------------------------------------
261
+ Get the list of users followed by a user on the Hub.
262
+
263
+
264
+ ORGANIZATIONS (3 methods)
265
+ ================================================================================
266
+
267
+ get_organization_overview(organization: str, token: ...) -> Organization
268
+ --------------------------------------------------------------------------------
269
+ Get an overview of an organization on the Hub.
270
+
271
+ list_organization_members(organization: str, token: ...) -> Iterable[User]
272
+ --------------------------------------------------------------------------------
273
+ List of members of an organization on the Hub.
274
+
275
+ list_organization_followers(organization: str, token: ...) -> Iterable[User]
276
+ --------------------------------------------------------------------------------
277
+ List followers of an organization on the Hub.
278
+
279
+
280
+ DISCUSSIONS & PULL REQUESTS (8 methods)
281
+ ================================================================================
282
+
283
+ create_discussion(repo_id: str, title: str, *, token: ..., description: ..., repo_type: ..., pull_request: bool = False) -> DiscussionWithDetails
284
+ --------------------------------------------------------------------------------
285
+ Creates a Discussion or Pull Request.
286
+
287
+ create_pull_request(repo_id: str, title: str, *, token: ..., description: ..., repo_type: ...) -> DiscussionWithDetails
288
+ --------------------------------------------------------------------------------
289
+ Creates a Pull Request. Pull Requests created programmatically will be in "draft" status.
290
+
291
+ get_discussion_details(repo_id: str, discussion_num: int, *, repo_type: ..., token: ...) -> DiscussionWithDetails
292
+ --------------------------------------------------------------------------------
293
+ Fetches a Discussion's / Pull Request's details from the Hub.
294
+
295
+ get_repo_discussions(repo_id: str, *, author: ..., discussion_type: ..., discussion_status: ..., repo_type: ..., token: ...) -> Iterator[Discussion]
296
+ --------------------------------------------------------------------------------
297
+ Fetches Discussions and Pull Requests for the given repo.
298
+
299
+ comment_discussion(repo_id: str, discussion_num: int, comment: str, *, token: ..., repo_type: ...) -> DiscussionComment
300
+ --------------------------------------------------------------------------------
301
+ Creates a new comment on the given Discussion.
302
+
303
+ edit_discussion_comment(repo_id: str, discussion_num: int, comment_id: str, new_content: str, *, token: ..., repo_type: ...) -> DiscussionComment
304
+ --------------------------------------------------------------------------------
305
+ Edits a comment on a Discussion / Pull Request.
306
+
307
+ hide_discussion_comment(repo_id: str, discussion_num: int, comment_id: str, *, token: ..., repo_type: ...) -> DiscussionComment
308
+ --------------------------------------------------------------------------------
309
+ Hides a comment on a Discussion / Pull Request.
310
+
311
+ change_discussion_status(repo_id: str, discussion_num: int, status: str, *, token: ..., repo_type: ..., comment: ...) -> Discussion
312
+ --------------------------------------------------------------------------------
313
+ Changes the status of a Discussion or Pull Request.
314
+
315
+
316
+ ACCESS REQUESTS (GATED REPOS) (6 methods)
317
+ ================================================================================
318
+
319
+ list_pending_access_requests(repo_id: str, *, token: ..., repo_type: ...) -> List[AccessRequest]
320
+ --------------------------------------------------------------------------------
321
+ List pending access requests for a gated repo.
322
+
323
+ list_accepted_access_requests(repo_id: str, *, token: ..., repo_type: ...) -> List[AccessRequest]
324
+ --------------------------------------------------------------------------------
325
+ List accepted access requests for a gated repo.
326
+
327
+ list_rejected_access_requests(repo_id: str, *, token: ..., repo_type: ...) -> List[AccessRequest]
328
+ --------------------------------------------------------------------------------
329
+ List rejected access requests for a gated repo.
330
+
331
+ accept_access_request(repo_id: str, user: str, *, token: ..., repo_type: ...) -> None
332
+ --------------------------------------------------------------------------------
333
+ Accept access request to a gated repo.
334
+
335
+ reject_access_request(repo_id: str, user: str, *, token: ..., repo_type: ..., rejection_reason: ...) -> None
336
+ --------------------------------------------------------------------------------
337
+ Reject access request to a gated repo.
338
+
339
+ grant_access(repo_id: str, user: str, *, token: ..., repo_type: ...) -> None
340
+ --------------------------------------------------------------------------------
341
+ Grant access to a gated repo without an access request.
342
+
343
+
344
+ USER COLLECTIONS (8 methods)
345
+ ================================================================================
346
+
347
+ get_collection(collection_slug: str, *, token: ...) -> Collection
348
+ --------------------------------------------------------------------------------
349
+ Get a collection's details from the Hub.
350
+
351
+ create_collection(title: str, *, namespace: ..., description: ..., private: ..., token: ...) -> Collection
352
+ --------------------------------------------------------------------------------
353
+ Create a new collection on the Hub.
354
+
355
+ list_collections(*, owner: ..., item: ..., sort: ..., limit: ..., token: ...) -> Iterable[Collection]
356
+ --------------------------------------------------------------------------------
357
+ List collections on the Huggingface Hub, given some filters.
358
+
359
+ delete_collection(collection_slug: str, *, missing_ok: bool = False, token: ...) -> None
360
+ --------------------------------------------------------------------------------
361
+ Delete a collection on the Hub.
362
+
363
+ add_collection_item(collection_slug: str, item_id: str, item_type: CollectionItemType_T, *, note: ..., exists_ok: bool = False, token: ...) -> Collection
364
+ --------------------------------------------------------------------------------
365
+ Add an item to a collection on the Hub.
366
+
367
+ delete_collection_item(collection_slug: str, item_object_id: str, *, missing_ok: bool = False, token: ...) -> None
368
+ --------------------------------------------------------------------------------
369
+ Delete an item from a collection.
370
+
371
+ update_collection_item(collection_slug: str, item_object_id: str, *, note: ..., position: ..., token: ...) -> None
372
+ --------------------------------------------------------------------------------
373
+ Update an item in a collection.
374
+
375
+ update_collection_metadata(collection_slug: str, *, title: ..., description: ..., position: ..., private: ..., theme: ..., token: ...) -> Collection
376
+ --------------------------------------------------------------------------------
377
+ Update the metadata of a collection on the Hub.
378
+
379
+
380
+ USER INTERACTIONS (3 methods)
381
+ ================================================================================
382
+
383
+ like(repo_id: str, *, token: ..., repo_type: ...) -> None
384
+ --------------------------------------------------------------------------------
385
+ Like a given repo on the Hub (star).
386
+
387
+ unlike(repo_id: str, *, token: ..., repo_type: ...) -> None
388
+ --------------------------------------------------------------------------------
389
+ Unlike a given repo on the Hub (unstar).
390
+
391
+ auth_check(repo_id: str, *, repo_type: ..., token: ...) -> None
392
+ --------------------------------------------------------------------------------
393
+ Check if the provided user token has access to a specific repository on the Hugging Face Hub.