import json import gradio as gr import requests import base64 import markdown GITHUB = "https://api.github.com" # --------------------------------------------------------- # MCP RESOURCES (HTML Renderers) # --------------------------------------------------------- @gr.mcp.resource("ui://repo_info.html", mime_type="text/html+skybridge") def repo_info_ui(): return """
""" @gr.mcp.resource("ui://issues.html", mime_type="text/html+skybridge") def issues_ui(): return """ """ @gr.mcp.resource("ui://pulls.html", mime_type="text/html+skybridge") def pulls_ui(): return """ """ @gr.mcp.resource("ui://readme.html", mime_type="text/html+skybridge") def readme_ui(): return """ """ @gr.mcp.resource("ui://contributors.html", mime_type="text/html+skybridge") def contributors_ui(): return """ """ @gr.mcp.resource("ui://search.html", mime_type="text/html+skybridge") def search_ui(): return """ """ @gr.mcp.resource("ui://files.html", mime_type="text/html+skybridge") def files_ui(): return """ """ # --------------------------------------------------------- # HELPERS # --------------------------------------------------------- def gget(url, params=None): r = requests.get(url, params=params, headers={"Accept": "application/vnd.github+json"}) if r.status_code != 200: raise Exception(f"GitHub API error ({r.status_code}): {r.text}") return r.json() # --------------------------------------------------------- # MCP TOOLS (with widget metadata) # --------------------------------------------------------- def tool_meta(resource_ui): """Helper to generate the full _meta dict for tools""" return { "openai/outputTemplate": resource_ui, "openai/resultCanProduceWidget": True, "openai/widgetAccessible": True } @gr.mcp.tool(_meta=tool_meta("ui://repo_info.html")) def get_repo_info(full_name: str) -> str: d = gget(f"{GITHUB}/repos/{full_name}") info = { "full_name": d["full_name"], "description": d.get("description", ""), "stars": d["stargazers_count"], "forks": d["forks_count"], "open_issues": d["open_issues_count"], "html_url": d["html_url"] } return json.dumps(info) @gr.mcp.tool(_meta=tool_meta("ui://issues.html")) def list_issues(full_name: str, per_page: int = 10) -> str: issues = gget(f"{GITHUB}/repos/{full_name}/issues", {"per_page": per_page}) issues = [{"title": i["title"], "number": i["number"], "url": i["html_url"]} for i in issues] return json.dumps(issues) @gr.mcp.tool(_meta=tool_meta("ui://pulls.html")) def list_pull_requests(full_name: str, per_page: int = 10) -> str: prs = gget(f"{GITHUB}/repos/{full_name}/pulls", {"per_page": per_page}) prs = [{"title": p["title"], "number": p["number"], "url": p["html_url"]} for p in prs] return json.dumps(prs) @gr.mcp.tool(_meta=tool_meta("ui://readme.html")) def get_readme(full_name: str) -> str: d = gget(f"{GITHUB}/repos/{full_name}/readme") content = base64.b64decode(d["content"]).decode() html = markdown.markdown(content) data = {"html": html} return json.dumps(data) @gr.mcp.tool(_meta=tool_meta("ui://contributors.html")) def list_contributors(full_name: str, limit: int = 10) -> str: c = gget(f"{GITHUB}/repos/{full_name}/contributors") c = [{ "name": i["login"], "avatar": i["avatar_url"], "url": i["html_url"], "commits": i["contributions"] } for i in c[:limit]] return json.dumps(c) @gr.mcp.tool(_meta=tool_meta("ui://search.html")) def search_repos(query: str, language: str = None, per_page: int = 10) -> str: params = {"q": query, "per_page": per_page, "sort": "stars"} if language: params["q"] += f"+language:{language}" d = gget(f"{GITHUB}/search/repositories", params) d = [{"full_name": r["full_name"], "stars": r["stargazers_count"], "description": r["description"], "url": r["html_url"]} for r in d["items"]] return json.dumps(d) @gr.mcp.tool(_meta=tool_meta("ui://files.html")) def list_files(full_name: str) -> str: d = gget(f"{GITHUB}/repos/{full_name}/git/trees/HEAD?recursive=1") d = [{"path": i["path"], "type": i["type"], "url": f"https://github.com/{full_name}/blob/HEAD/{i['path']}"} for i in d["tree"]] return json.dumps(d) # --------------------------------------------------------- # Gradio UI # --------------------------------------------------------- with gr.Blocks() as demo: gr.Markdown("### GitHub Helper Bot — use inside ChatGPT") repo_input = gr.Textbox(label="Repository Full Name (owner/repo)") btn_repo = gr.Button("Get Repo Info") btn_issues = gr.Button("List Issues") btn_prs = gr.Button("List Pull Requests") btn_readme = gr.Button("Get README") btn_contrib = gr.Button("List Contributors") btn_files = gr.Button("List Files") search_query = gr.Textbox(label="Search Repositories") btn_search = gr.Button("Search Repos") tool_response = gr.TextArea(label="Tool Response") html_out = gr.TextArea(label="HTML Output") # Wire tools + resources btn_repo.click(get_repo_info, inputs=repo_input, outputs=tool_response) btn_repo.click(repo_info_ui, outputs=html_out) btn_issues.click(list_issues, inputs=repo_input, outputs=tool_response) btn_issues.click(issues_ui, outputs=html_out) btn_prs.click(list_pull_requests, inputs=repo_input, outputs=tool_response) btn_prs.click(pulls_ui, outputs=html_out) btn_readme.click(get_readme, inputs=repo_input, outputs=tool_response) btn_readme.click(readme_ui, outputs=html_out) btn_contrib.click(list_contributors, inputs=repo_input, outputs=tool_response) btn_contrib.click(contributors_ui, outputs=html_out) btn_files.click(list_files, inputs=repo_input, outputs=tool_response) btn_files.click(files_ui, outputs=html_out) btn_search.click(search_repos, inputs=search_query, outputs=tool_response) btn_search.click(search_ui, outputs=html_out) if __name__ == "__main__": demo.launch(share=True, mcp_server=True)