Sandhya commited on
Commit
356bee1
Β·
1 Parent(s): 1157887

pull_request

Browse files
Files changed (4) hide show
  1. README.md +15 -7
  2. app.py +482 -0
  3. mcp_server.py +156 -0
  4. pyproject.toml +25 -0
README.md CHANGED
@@ -1,13 +1,21 @@
1
  ---
2
- title: Discussion Bot Webhook
3
- emoji: πŸ“‰
4
- colorFrom: yellow
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 5.38.0
8
  app_file: app.py
9
  pinned: false
10
- license: mit
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: tag PR Agent bot
3
+ emoji: πŸ‘€
4
+ colorFrom: purple
5
+ colorTo: yellow
6
  sdk: gradio
7
+ sdk_version: 5.31.0
8
  app_file: app.py
9
  pinned: false
10
+ base_path: /gradio
11
  ---
12
 
13
+ # HF Tagging Bot
14
+
15
+ This is a bot that tags HuggingFace models when they are mentioned in discussions.
16
+
17
+ ## How it works
18
+
19
+ 1. The bot listens to discussions on the HuggingFace Hub
20
+ 2. When a discussion is created, the bot checks for tag mentions in the comment
21
+ 3. If a tag is mentioned, the bot adds the tag to the model repository via a PR
app.py ADDED
@@ -0,0 +1,482 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # ----------------------------------------------------------------------------------
2
+ # 1. Agent Configuration
3
+ # ----------------------------------------------------------------------------------
4
+
5
+ import os
6
+ import re
7
+ import json
8
+ from datetime import datetime
9
+ from typing import List, Dict, Any, Optional, Literal
10
+
11
+ from fastapi import FastAPI, Request, BackgroundTasks
12
+ from fastapi.middleware.cors import CORSMiddleware
13
+ import gradio as gr
14
+ import uvicorn
15
+ from pydantic import BaseModel
16
+ from huggingface_hub.inference._mcp.agent import Agent
17
+ from dotenv import load_dotenv
18
+ load_dotenv()
19
+
20
+ HF_TOKEN=os.getenv("HF_TOKEN")
21
+ WEBHOOK_SECRET=os.getenv("WEBHOOK_SECRET")
22
+ HF_MODEL=os.getenv("HF_MODEL","microsoft/DialoGPT-medium")
23
+ DEFAULT_PROVIDER:Literal['hf-inference']="hf-inference"
24
+ HF_PROVIDER=os.getenv("HF_PROVIDER",DEFAULT_PROVIDER)
25
+ agent_instance: Optional[Agent]=None
26
+ tag_operations_store:List[Dict[str,Any]]=[]
27
+
28
+ RECOGNIZED_TAGS = {
29
+ "pytorch",
30
+ "tensorflow",
31
+ "jax",
32
+ "transformers",
33
+ "diffusers",
34
+ "text-generation",
35
+ "text-classification",
36
+ "question-answering",
37
+ "text-to-image",
38
+ "image-classification",
39
+ "object-detection",
40
+ " ",
41
+ "fill-mask",
42
+ "token-classification",
43
+ "translation",
44
+ "summarization",
45
+ "feature-extraction",
46
+ "sentence-similarity",
47
+ "zero-shot-classification",
48
+ "image-to-text",
49
+ "automatic-speech-recognition",
50
+ "audio-classification",
51
+ "voice-activity-detection",
52
+ "depth-estimation",
53
+ "image-segmentation",
54
+ "video-classification",
55
+ "reinforcement-learning",
56
+ "tabular-classification",
57
+ "tabular-regression",
58
+ "time-series-forecasting",
59
+ "graph-ml",
60
+ "robotics",
61
+ "computer-vision",
62
+ "nlp",
63
+ "cv",
64
+ "multimodal",
65
+ }
66
+
67
+
68
+ class WebhookEvent(BaseModel):
69
+ event: Dict[str, str]
70
+ comment: Dict[str, Any]
71
+ discussion: Dict[str, Any]
72
+ repo: Dict[str, str]
73
+
74
+
75
+ app = FastAPI(title="HF Tagging Bot")
76
+ app.add_middleware(CORSMiddleware, allow_origins=["*"])
77
+
78
+
79
+ async def get_agent():
80
+ """Get or create Agent instance"""
81
+ print("πŸ€– get_agent() called...")
82
+ global agent_instance
83
+ if agent_instance is None and HF_TOKEN:
84
+ print("πŸ”§ Creating new Agent instance...")
85
+ print(f"πŸ”‘ HF_TOKEN present: {bool(HF_TOKEN)}")
86
+ print(f"πŸ€– Model: {HF_MODEL}")
87
+ print(f"πŸ”— Provider: {DEFAULT_PROVIDER}")
88
+
89
+ try:
90
+ agent_instance = Agent(
91
+ model=HF_MODEL,
92
+ provider=DEFAULT_PROVIDER,
93
+ api_key=HF_TOKEN,
94
+ servers=[
95
+ {
96
+ "type": "stdio",
97
+ "config": {
98
+ "command": "python",
99
+ "args": ["mcp_server.py"],
100
+ "cwd": ".",
101
+ "env": {"HF_TOKEN": HF_TOKEN} if HF_TOKEN else {},
102
+ },
103
+ }
104
+ ],
105
+ )
106
+ print("βœ… Agent instance created successfully")
107
+ print("πŸ”§ Loading tools...")
108
+ await agent_instance.load_tools()
109
+ print("βœ… Tools loaded successfully")
110
+ except Exception as e:
111
+ print(f"❌ Error creating/loading agent: {str(e)}")
112
+ agent_instance = None
113
+ elif agent_instance is None:
114
+ print("❌ No HF_TOKEN available, cannot create agent")
115
+ else:
116
+ print("βœ… Using existing agent instance")
117
+
118
+ return agent_instance
119
+
120
+
121
+ def extract_tags_from_text(text: str) -> List[str]:
122
+ """Extract potential tags from discussion text"""
123
+ text_lower = text.lower()
124
+ explicit_tags = []
125
+ tag_pattern = r"tags?:\s*([a-zA-Z0-9-_,\s]+)"
126
+ matches = re.findall(tag_pattern, text_lower)
127
+ for match in matches:
128
+ tags = [tag.strip() for tag in match.split(",")]
129
+ explicit_tags.extend(tags)
130
+ hashtag_pattern = r"#([a-zA-Z0-9-_]+)"
131
+ hashtag_matches = re.findall(hashtag_pattern, text_lower)
132
+ explicit_tags.extend(hashtag_matches)
133
+
134
+ mentioned_tags = []
135
+ for tag in RECOGNIZED_TAGS:
136
+ if tag in text_lower:
137
+ mentioned_tags.append(tag)
138
+
139
+ all_tags = list(set(explicit_tags + mentioned_tags))
140
+
141
+ valid_tags = []
142
+ for tag in all_tags:
143
+ if tag in RECOGNIZED_TAGS or tag in explicit_tags:
144
+ valid_tags.append(tag)
145
+
146
+ return valid_tags
147
+
148
+
149
+ async def process_webhook_comment(webhook_data: Dict[str, Any]):
150
+ """Process webhook to detect and add tags"""
151
+ print("🏷️ Starting process_webhook_comment...")
152
+
153
+ try:
154
+ comment_content = webhook_data["comment"]["content"]
155
+ discussion_title = webhook_data["discussion"]["title"]
156
+ repo_name = webhook_data["repo"]["name"]
157
+ discussion_num = webhook_data["discussion"]["num"]
158
+ comment_author = webhook_data["comment"]["author"].get("id", "unknown")
159
+
160
+ print(f"πŸ“ Comment content: {comment_content}")
161
+ print(f"πŸ“° Discussion title: {discussion_title}")
162
+ print(f"πŸ“¦ Repository: {repo_name}")
163
+
164
+ comment_tags = extract_tags_from_text(comment_content)
165
+ title_tags = extract_tags_from_text(discussion_title)
166
+ all_tags = list(set(comment_tags + title_tags))
167
+
168
+ print(f"πŸ” Comment tags found: {comment_tags}")
169
+ print(f"πŸ” Title tags found: {title_tags}")
170
+ print(f"🏷️ All unique tags: {all_tags}")
171
+
172
+ result_messages = []
173
+
174
+ if not all_tags:
175
+ msg = "No recognizable tags found in the discussion."
176
+ print(f"❌ {msg}")
177
+ result_messages.append(msg)
178
+ else:
179
+ print("πŸ€– Getting agent instance...")
180
+ agent = await get_agent()
181
+ if not agent:
182
+ msg = "Error: Agent not configured (missing HF_TOKEN)"
183
+ print(f"❌ {msg}")
184
+ result_messages.append(msg)
185
+ else:
186
+ print("βœ… Agent instance obtained successfully")
187
+
188
+ try:
189
+ user_prompt = f"""
190
+ I need to add the following tags to the repository '{repo_name}': {", ".join(all_tags)}
191
+
192
+ For each tag, please:
193
+ 1. Check if the tag already exists on the repository using get_current_tags
194
+ 2. If the tag doesn't exist, add it using add_new_tag
195
+ 3. Provide a summary of what was done for each tag
196
+
197
+ Please process all {len(all_tags)} tags: {", ".join(all_tags)}
198
+ """
199
+
200
+ print("πŸ’¬ Sending comprehensive prompt to agent...")
201
+ print(f"πŸ“ Prompt: {user_prompt}")
202
+
203
+ conversation_result = []
204
+
205
+ try:
206
+ async for item in agent.run(user_prompt):
207
+ item_str = str(item)
208
+ conversation_result.append(item_str)
209
+
210
+ if (
211
+ "tool_call" in item_str.lower()
212
+ or "function" in item_str.lower()
213
+ ):
214
+ print(f"πŸ”§ Agent using tools: {item_str[:200]}...")
215
+ elif "content" in item_str and len(item_str) < 500:
216
+ print(f"πŸ’­ Agent response: {item_str}")
217
+
218
+ full_response = " ".join(conversation_result)
219
+ print(f"πŸ“‹ Agent conversation completed successfully")
220
+
221
+ for tag in all_tags:
222
+ tag_mentioned = tag.lower() in full_response.lower()
223
+
224
+ if (
225
+ "already exists" in full_response.lower()
226
+ and tag_mentioned
227
+ ):
228
+ msg = f"Tag '{tag}': Already exists"
229
+ elif (
230
+ "pr" in full_response.lower()
231
+ or "pull request" in full_response.lower()
232
+ ):
233
+ if tag_mentioned:
234
+ msg = f"Tag '{tag}': PR created successfully"
235
+ else:
236
+ msg = (
237
+ f"Tag '{tag}': Processed "
238
+ "(PR may have been created)"
239
+ )
240
+ elif "success" in full_response.lower() and tag_mentioned:
241
+ msg = f"Tag '{tag}': Successfully processed"
242
+ elif "error" in full_response.lower() and tag_mentioned:
243
+ msg = f"Tag '{tag}': Error during processing"
244
+ else:
245
+ msg = f"Tag '{tag}': Processed by agent"
246
+
247
+ print(f"βœ… Result for tag '{tag}': {msg}")
248
+ result_messages.append(msg)
249
+
250
+ except Exception as agent_error:
251
+ print(f"⚠️ Agent streaming failed: {str(agent_error)}")
252
+ print("πŸ”„ Falling back to direct MCP tool calls...")
253
+
254
+ try:
255
+ import sys
256
+ import importlib.util
257
+
258
+ spec = importlib.util.spec_from_file_location(
259
+ "mcp_server", "./mcp_server.py"
260
+ )
261
+ mcp_module = importlib.util.module_from_spec(spec)
262
+ spec.loader.exec_module(mcp_module)
263
+ for tag in all_tags:
264
+ try:
265
+ print(
266
+ f"πŸ”§ Directly calling get_current_tags for '{tag}'"
267
+ )
268
+ current_tags_result = mcp_module.get_current_tags(
269
+ repo_name
270
+ )
271
+ print(
272
+ f"πŸ“„ Current tags result: {current_tags_result}"
273
+ )
274
+
275
+ import json
276
+
277
+ tags_data = json.loads(current_tags_result)
278
+
279
+ if tags_data.get("status") == "success":
280
+ current_tags = tags_data.get("current_tags", [])
281
+ if tag in current_tags:
282
+ msg = f"Tag '{tag}': Already exists"
283
+ print(f"βœ… {msg}")
284
+ else:
285
+ print(
286
+ f"πŸ”§ Directly calling add_new_tag for '{tag}'"
287
+ )
288
+ add_result = mcp_module.add_new_tag(
289
+ repo_name, tag
290
+ )
291
+ print(f"πŸ“„ Add tag result: {add_result}")
292
+
293
+ add_data = json.loads(add_result)
294
+ if add_data.get("status") == "success":
295
+ pr_url = add_data.get("pr_url", "")
296
+ msg = f"Tag '{tag}': PR created - {pr_url}"
297
+ elif (
298
+ add_data.get("status")
299
+ == "already_exists"
300
+ ):
301
+ msg = f"Tag '{tag}': Already exists"
302
+ else:
303
+ msg = f"Tag '{tag}': {add_data.get('message', 'Processed')}"
304
+ print(f"βœ… {msg}")
305
+ else:
306
+ error_msg = tags_data.get(
307
+ "error", "Unknown error"
308
+ )
309
+ msg = f"Tag '{tag}': Error - {error_msg}"
310
+ print(f"❌ {msg}")
311
+
312
+ result_messages.append(msg)
313
+
314
+ except Exception as direct_error:
315
+ error_msg = f"Tag '{tag}': Direct call error - {str(direct_error)}"
316
+ print(f"❌ {error_msg}")
317
+ result_messages.append(error_msg)
318
+ except Exception as fallback_error:
319
+ error_msg = f"Fallback approach failed: {str(fallback_error)}"
320
+ print(f"❌ {error_msg}")
321
+ result_messages.append(error_msg)
322
+ except Exception as e:
323
+ error_msg = f"Error during agent processing {str(e)}"
324
+ print(f"❌ {error_msg}")
325
+ result_messages.append(error_msg)
326
+ base_url="https://huggingface.co"
327
+ discussion_url=f"{base_url}/{repo_name}/discussion/{discussion_num}"
328
+ interaction = {
329
+ "timestamp": datetime.now().isoformat(),
330
+ "repo": repo_name,
331
+ "discussion_title": discussion_title,
332
+ "discussion_num": discussion_num,
333
+ "discussion_url": discussion_url,
334
+ "original_comment": comment_content,
335
+ "comment_author": comment_author,
336
+ "detected_tags": all_tags,
337
+ "results": result_messages,
338
+ }
339
+ tag_operations_store.append(interaction)
340
+ final_result="|".join(result_messages)
341
+ print(f"πŸ’Ύ Stored interaction and returning result: {final_result}")
342
+ return final_result
343
+
344
+ except Exception as e:
345
+ error_msg = f"❌ Fatal error in process_webhook_comment: {str(e)}"
346
+ print(error_msg)
347
+ return error_msg
348
+
349
+
350
+
351
+
352
+
353
+ @app.get("/")
354
+ async def root():
355
+ """Root endpoint with basic information"""
356
+ return {
357
+ "name":"HF Tagging Bot",
358
+ "status":"running",
359
+ "description":"Webhook listener for automatic model tagging",
360
+ "endpoints":{
361
+ "webhook":"/webhook",
362
+ "health":"/health",
363
+ "operations":"/operations"
364
+ }
365
+ }
366
+
367
+ @app.get("/health")
368
+ async def health_check():
369
+ """Health check endpoint for monitoring"""
370
+ agent=await get_agent()
371
+ return {
372
+ "status":"healthy",
373
+ "timestamp":datetime.now().isoformat(),
374
+ "components":{
375
+ "webhook_secret":"configured" if WEBHOOK_SECRET else "missing",
376
+ "hf_token":"configured" if HF_TOKEN else "missing",
377
+ "mcp_agent":"ready" if agent else "not ready"
378
+ }
379
+ }
380
+
381
+ @app.get("/operations")
382
+ async def get_operations():
383
+ """Get recent tag operations for monitoring"""
384
+ recent_ops=tag_operations_store[-50:] if tag_operations_store else []
385
+ return {
386
+ "total_operations":len(tag_operations_store),
387
+ "recent_operations":recent_ops
388
+ }
389
+
390
+
391
+
392
+
393
+
394
+
395
+
396
+ @app.post("/Webhook")
397
+ async def webhook_handler(request:Request, background_tasks:BackgroundTasks):
398
+ """
399
+ Handle incoming webhooks from Hugging Face Hub
400
+ Following the pattern from: https://raw.githubusercontent.com/huggingface/hub-docs/refs/heads/main/docs/hub/webhooks-guide-discussion-bot.md
401
+ """
402
+ print("πŸ”” Webhook received!")
403
+ webhook_secret=request.headers.get("X-webhook-Secret")
404
+ if webhook_secret!=WEBHOOK_SECRET:
405
+ print("❌ Invalid webhook secret")
406
+ return {"error":"incorrect secret"}
407
+ payload=await request.json()
408
+ print(f"πŸ“₯ Received webhook payload: {json.dumps(payload, indent=2)}")
409
+ event=payload.get("event",{})
410
+ scope=event.get("score")
411
+ action=event.get("action")
412
+
413
+ print(f"πŸ” Event details - scope: {scope}, action: {action}")
414
+ scope_check = scope == "discussion"
415
+ action_check = action == "create"
416
+ not_pr = not payload["discussion"]["isPullRequest"]
417
+ scope_check = scope_check and not_pr
418
+ print(f"βœ… not_pr: {not_pr}")
419
+ print(f"βœ… scope_check: {scope_check}")
420
+ print(f"βœ… action_check: {action_check}")
421
+
422
+ if scope_check and action_check:
423
+ required_fields=['comment','discussion','repo']
424
+ missing_fields=[field for field in required_fields if field not in payload]
425
+
426
+ if missing_fields:
427
+ error_msg = f"Missing required fields: {missing_fields}"
428
+ print(f"❌ {error_msg}")
429
+ return {"error": error_msg}
430
+ print(f"πŸš€ Processing webhook for repo: {payload['repo']['name']}")
431
+ background_tasks.add_task(process_webhook_comment,payload)
432
+ return {"status":"processing"}
433
+ print(f"⏭️ Ignoring webhook - scope: {scope}, action: {action}")
434
+ return {"status": "ignored"}
435
+
436
+ @app.post("/simulate_webhook")
437
+ async def simulate_webhook(repo_name:str,discussion_title:str,comment_content:str)->str:
438
+ """Simulate webhook for testing purposes"""
439
+ if not all([repo_name,discussion_title,comment_content]):
440
+ return "please fill in all fields"
441
+ mock_payload={
442
+ "event":{"action":"create","scope":"discussion.comment"},
443
+ "comment":{"content":comment_content,"author":{"id":"test-user"},"id":"mock-comment-id","hidden":False},
444
+ "discussion":{"title":discussion_title,"num":len(tag_operations_store)+1,"id":"mock-comment-id","status":"open","isPullRequest":False},
445
+ "repo":{"name":repo_name,"type":"model","private":False}
446
+ }
447
+ response=await process_webhook_comment(mock_payload)
448
+ return f"βœ… Processed! Results: {response}"
449
+
450
+ def create_gradio_app():
451
+ """Create Gradio interface"""
452
+ with gr.Blocks(title="HF Tagging Bot", theme=gr.themes.Soft()) as demo:
453
+ gr.Markdown("# 🏷️ HF Tagging Bot Dashboard")
454
+ gr.Markdown("*Automatically adds tags to models when mentioned in discussions*")
455
+
456
+ gr.Markdown("""
457
+ ## How it works:
458
+ - Monitors HuggingFace Hub discussions
459
+ - Detects tag mentions in comments (e.g., "tag: pytorch",
460
+ "#transformers")
461
+ - Automatically adds recognized tags to the model repository
462
+ - Supports common ML tags like: pytorch, tensorflow,
463
+ text-generation, etc.
464
+ """)
465
+ with gr.Column():
466
+ sim_repo=gr.Textbox(label="Repository",value="burtenshaw/play-mcp-repo-bot",placeholder="Sandhya2002/tag-boat")
467
+ sim_title= gr.Textbox(label="Discussion Title",value="Add pytorch tag",placeholder="Discussion title")
468
+ sim_comment=gr.Textbox(label="comment",lines=3,value="This model should have tags: pytorch, text-generation",placeholder="Comment mentioning tags ...")
469
+ sim_btn=gr.Button("🏷️ Test Tag Detection")
470
+ with gr.Column():
471
+ sim_result=gr.Textbox(label="Result",lines=8)
472
+ sim_btn.click(fn=simulate_webhook,inputs=[sim_repo,sim_title,sim_comment],outputs=sim_result)
473
+ gr.Markdown(f"""## Recognized Tags: {",".join(sorted(RECOGNIZED_TAGS))}""")
474
+ return demo
475
+ gradio_app=create_gradio_app()
476
+ app=gr.mount_gradio_app(app,gradio_app,path="/gradio")
477
+
478
+ if __name__=="__main__":
479
+ print("πŸš€ Starting HF Tagging Bot...")
480
+ print("πŸ“Š Dashboard: http://localhost:7860/gradio")
481
+ print("πŸ”— Webhook: http://localhost:7860/webhook")
482
+ unicorn.run("app:app",host="0.0.0.0",port=7860,reload=True)
mcp_server.py ADDED
@@ -0,0 +1,156 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --------------------------------------------------------------------------------------
2
+ # 1. Imports and Configuration
3
+ # ---------------------------------------------------------------------------------------
4
+
5
+ import os
6
+ import json
7
+ from fastmcp import FastMCP
8
+ from huggingface_hub import HfApi, model_info, ModelCard, ModelCardData
9
+ from huggingface_hub.utils import HfHubHTTPError
10
+ from huggingface_hub import CommitOperationAdd
11
+ import traceback
12
+ from dotenv import load_dotenv
13
+ load_dotenv()
14
+
15
+
16
+ HF_TOKEN=os.getenv("HF_TOKEN")
17
+ hf_api=HfApi(token=HF_TOKEN) if HF_TOKEN else None
18
+ mcp=FastMCP("hf-tagging-bot")
19
+
20
+ # -------------------------------------------------------------------------------
21
+ # 2. Get Current Tags Tool
22
+ # -------------------------------------------------------------------------------
23
+
24
+ @mcp.tool()
25
+ def get_current_tags(repo_id:str)->str:
26
+ """Get Current tags from a HuggingFace model repository"""
27
+ print(f"πŸ”§ get_current_tags called with repo_id: {repo_id}")
28
+ if not hf_api:
29
+ error_result={"error":"HF token not configured"}
30
+ json_str=json.dumps(error_result)
31
+ print(f"❌ No HF API token-returning:{json_str}")
32
+ return json_str
33
+
34
+ try:
35
+ print(f"πŸ“‘Fetching model info for: {repo_id}")
36
+ info=model_info(repo_id=repo_id,token=HF_TOKEN)
37
+ current_tags= info.tags if info.tags else []
38
+ print(f"🏷️ Found {len(current_tags)} tags: {current_tags}")
39
+
40
+ result={
41
+ "status":"success",
42
+ "repo_id":repo_id,
43
+ "current_tags":current_tags,
44
+ "count":len(current_tags)
45
+ }
46
+ json_str=json.dumps(result)
47
+ print(f"βœ… get_current_tags returning : {json_str}")
48
+ return json_str
49
+ except Exception as e:
50
+ print(f"❌ Error in get_current_tags: {str(e)}")
51
+ error_result={
52
+ "status":"success",
53
+ "repo_id":repo_id,
54
+ "error":str(e)
55
+ }
56
+ json_str=json.dumps(error_result)
57
+ print(f"❌ get_current_tags error returning : {json_str}")
58
+ return json_str
59
+
60
+ # --------------------------------------------------------------------------------------------------------
61
+ # 3. 3. Add New Tag Tool
62
+ # --------------------------------------------------------------------------------------------------------
63
+
64
+ @mcp.tool()
65
+ def add_new_tag(repo_id:str,new_tag:str)->str:
66
+ """Add a new tag to a HuggingFace model repository via PR"""
67
+ print(f"πŸ”§ add_new_tag called with repo_id: {repo_id}, new_tag:{new_tag}")
68
+ if not hf_api:
69
+ error_result={"error":"HF token not configured"}
70
+ json_str=json.dumps(error_result)
71
+ print(f"❌ No HF API token-returning:{json_str}")
72
+ return json_str
73
+ try:
74
+ print(f"πŸ“‘Fetching model info for: {repo_id}")
75
+ info=model_info(repo_id=repo_id,token=HF_TOKEN)
76
+ current_tags= info.tags if info.tags else []
77
+ print(f"🏷️ Found {len(current_tags)} tags: {current_tags}")
78
+
79
+ if new_tag in current_tags:
80
+ print(f"⚠️ Tag '{new_tag}' already exists in {current_tags}")
81
+ result={
82
+ "status":"already_exists",
83
+ "repo_id":repo_id,
84
+ "tag":new_tag,
85
+ "message":f"Tag '{new_tag}' already exists"
86
+ }
87
+ json_str=json.dumps(result)
88
+ print(f"🏷️ add_new_tag (already exists) returning : {json_str}")
89
+ return json_str
90
+ updated_tags=current_tags+[new_tag]
91
+ print(f"πŸ†• will update tags from {current_tags} to {updated_tags}")
92
+ try:
93
+ print(f"πŸ“„ loading existing model card...")
94
+ card=ModelCard.load(repo_id,token=HF_TOKEN)
95
+ if not hasattr(card,"data") or card.data is None:
96
+ card.data=ModelCardData()
97
+ except HfHubHTTPError:
98
+ print(f"πŸ“„ creating new model card (none exists)")
99
+ card=ModelCard("")
100
+ card.data=ModelCardData()
101
+ card_dict=card.data.to_dict()
102
+ card_dict['tags']=updated_tags
103
+ card.data=ModelCardData(**card_dict)
104
+ pr_title=f"Add '{new_tag}' tag"
105
+ pr_description=f"""
106
+ ## Add tag: {new_tag}
107
+
108
+ This PR adds the '{new_tag}' tag to the model repository.
109
+
110
+ **changes:**
111
+ - Added '{new_tag}' to model tags
112
+ - Updated from {len(current_tags)} to {len(updated_tags)} tags
113
+
114
+
115
+ **current tags:** {",".join(current_tags) if current_tags else "None"}
116
+ **New tags:** {",".join(updated_tags)}
117
+
118
+ πŸ€–This is a pull request created by the HuggingFace Hub Tagging Bot.
119
+
120
+ """
121
+ print(f"πŸš€ creating PR with title: {pr_title}")
122
+ commit_info=hf_api.create_commit(
123
+ repo_id=repo_id,
124
+ operations=[CommitOperationAdd(path_in_repo="README.md",path_or_fileobj=str(card).encode("utf-8"))],
125
+ commit_message=pr_title,commit_description=pr_description,token=HF_TOKEN,create_pr=True
126
+ )
127
+ pr_url_attr=commit_info.pr_url
128
+ pr_url=pr_url_attr if hasattr(commit_info,"prl_url") else str(commit_info)
129
+ print(f"βœ… PR created successfully! URL: {pr_url}")
130
+ result={
131
+ "status":"success",
132
+ "repo_id":repo_id,
133
+ "tag":new_tag,
134
+ "prl_url":pr_url,
135
+ "previous_tags":current_tags,
136
+ "new_tags":updated_tags,
137
+ "message":f"created PR to add tag '{new_tag}'"
138
+ }
139
+ json_str=json.dumps(result)
140
+ print(f"βœ… add_new_tag success returning: {json_str}")
141
+ return json_str
142
+ except Exception as e:
143
+ print(f"❌ Error in add_new_tag: {str(e)}")
144
+ print(f"❌ Error type: {type(e)}")
145
+ print(f"❌ Traceback: {traceback.format_exc()}")
146
+ error_result={
147
+ "status":"error",
148
+ "repo_id":repo_id,
149
+ "tag":new_tag,
150
+ "error":str(e)
151
+ }
152
+ json_str=json.dumps(error_result)
153
+ print(f"❌ add_new_tag error returning: {json_str}")
154
+ return json_str
155
+
156
+
pyproject.toml ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [project]
2
+ name = "mcp-PR-Agent"
3
+ version = "0.1.0"
4
+ description = "FastAPI and Gradio app for Hugging Face Hub discussion webhooks"
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ dependencies = [
8
+ "fastapi>=0.104.0",
9
+ "uvicorn[standard]>=0.24.0",
10
+ "gradio>=4.0.0",
11
+ "huggingface-hub[mcp]>=0.32.0",
12
+ "pydantic>=2.0.0",
13
+ "python-multipart>=0.0.6",
14
+ "requests>=2.31.0",
15
+ "python-dotenv>=1.0.0",
16
+ "fastmcp>=2.0.0",
17
+ ]
18
+
19
+
20
+ [build-system]
21
+ requires = ["hatchling"]
22
+ build-backend = "hatchling.build"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src"]