Skip to main content
Glama
resources.py11 kB
"""GitHub MCP resource handlers for URI-based content access.""" import json import re from typing import Any, Optional from github_client import GitHubClient from utils import is_text, format_file_size, is_binary_file def parse_gh_uri(uri: str) -> tuple[str, str, str, Optional[str]]: """Parse GitHub URI to extract owner, repo, path, and optional ref. Args: uri: GitHub URI (gh-file://owner/repo/path or gh-pr-diff://owner/repo/number) Returns: Tuple of (owner, repo, path, ref) """ # gh-file://owner/repo/path[?ref=branch] file_pattern = r"^gh-file://([^/]+)/([^/]+)/(.+?)(?:\?ref=([^&]+))?$" file_match = re.match(file_pattern, uri) if file_match: owner, repo, path, ref = file_match.groups() return owner, repo, path, ref or "HEAD" # gh-pr-diff://owner/repo/number pr_pattern = r"^gh-pr-diff://([^/]+)/([^/]+)/(.+)$" pr_match = re.match(pr_pattern, uri) if pr_match: owner, repo, number = pr_match.groups() return owner, repo, number, None raise ValueError(f"Unsupported URI scheme: {uri}") def get_pr_diff_resource(owner: str, repo: str, number: str) -> dict[str, Any]: """Get PR diff as a resource. Args: owner: Repository owner repo: Repository name number: PR number Returns: Resource data with content and metadata """ try: client = GitHubClient() repository = client.get_repository(owner, repo) pull_request = repository.get_pull(int(number)) # Get diff diff = pull_request.get_files() diff_data = [] total_additions = 0 total_deletions = 0 for file in diff: # 트렁케이션 정보 추가 patch_content = file.patch[:2000] if file.patch else None truncated = len(file.patch) > 2000 if file.patch else False file_data = { "filename": file.filename, "status": file.status, "additions": file.additions, "deletions": file.deletions, "changes": file.changes, "patch": patch_content, "truncated": truncated, "original_length": len(file.patch) if file.patch else 0, "raw_url": file.raw_url, } diff_data.append(file_data) total_additions += file.additions total_deletions += file.deletions # Create summary summary = f"""Pull Request #{number}: {pull_request.title} Repository: {repository.full_name} Author: @{pull_request.user.login} State: {pull_request.state} Files changed: {len(diff_data)} Total additions: +{total_additions} Total deletions: -{total_deletions} Changed files: """ for file in diff_data[:10]: # Show first 10 files summary += f" {file['filename']} ({file['status']}) +{file['additions']} -{file['deletions']}\n" if len(diff_data) > 10: summary += f"\n... and {len(diff_data) - 10} more files" data = { "summary": summary, "data": diff_data, "success": True, "file_count": len(diff_data), "total_additions": total_additions, "total_deletions": total_deletions, } # JSON 중복 처리 제거 - 한 번만 생성 resource_data = json.dumps(data, indent=2) metadata = { "name": f"PR #{number} Diff", "description": f"Diff for pull request #{number} in {owner}/{repo}", "mime_type": "application/json", "size": len(resource_data), # 실제 반환 크기 "uri": f"gh-pr-diff://{owner}/{repo}/{number}", "source": f"https://github.com/{owner}/{repo}/pull/{number}", } return {"content": resource_data, "metadata": metadata} except Exception as e: error_data = { "error": str(e), "success": False, "uri": f"gh-pr-diff://{owner}/{repo}/{number}", } resource_data = json.dumps(error_data, indent=2) metadata = { "name": f"PR #{number} Diff Error", "description": f"Error retrieving diff for PR #{number}", "mime_type": "application/json", "size": len(resource_data), "uri": f"gh-pr-diff://{owner}/{repo}/{number}", "error": True, } return {"content": resource_data, "metadata": metadata} def get_file_resource( owner: str, repo: str, path: str, ref: str = "HEAD" ) -> dict[str, Any]: """Get file or directory as a resource. Args: owner: Repository owner repo: Repository name path: File path in repository ref: Git reference (branch, tag, or commit SHA) Returns: Resource data with content and metadata """ try: client = GitHubClient() repository = client.get_repository(owner, repo) # Get file content file_content = repository.get_contents(path, ref=ref) if isinstance(file_content, list): # Directory files = [] for item in file_content: file_data = { "name": item.name, "path": item.path, "type": item.type, "size": item.size, "size_formatted": format_file_size(item.size), "url": item.html_url, "download_url": item.download_url, } files.append(file_data) summary = f"""Directory: {path} Repository: {repository.full_name} Reference: {ref} Files: {len(files)} Contents: """ for file in files[:10]: # Show first 10 files summary += ( f" {file['name']} ({file['type']}, {file['size_formatted']})\n" ) if len(files) > 10: summary += f"\n... and {len(files) - 10} more items" data = { "summary": summary, "data": files, "success": True, "type": "directory", "file_count": len(files), } # JSON 중복 처리 제거 resource_data = json.dumps(data, indent=2) metadata = { "name": f"Directory: {path}", "description": f"Directory contents for {path} in {owner}/{repo}", "mime_type": "application/json", "size": len(resource_data), "uri": f"gh-file://{owner}/{repo}/{path}?ref={ref}", "source": f"https://github.com/{owner}/{repo}/tree/{ref}/{path}", "type": "directory", } return {"content": resource_data, "metadata": metadata} else: # Single file content = file_content.decoded_content is_text_content = is_text(content) # 바이너리 감지 개선 is_binary = is_binary_file(file_content.name) or ( hasattr(file_content, "content_type") and file_content.content_type and not file_content.content_type.startswith("text/") ) file_data = { "name": file_content.name, "path": file_content.path, "type": file_content.type, "original_size": file_content.size, # 원본 크기 "size_formatted": format_file_size(file_content.size), "encoding": file_content.encoding, "url": file_content.html_url, "download_url": file_content.download_url, "is_text": is_text_content, "is_binary": is_binary, } if is_text_content and file_content.size < 1024 * 1024: # Less than 1MB try: file_data["content"] = content.decode("utf-8") file_data["content_size"] = len( file_data["content"] ) # 실제 반환 크기 except UnicodeDecodeError: file_data["content"] = content.decode("utf-8", errors="replace") file_data["content_size"] = len(file_data["content"]) else: file_data["content"] = None file_data["content_size"] = 0 if file_content.size >= 1024 * 1024: file_data["content_note"] = "File too large to display (>1MB)" elif is_binary: file_data["content_note"] = "Binary file - content not displayed" else: file_data["content_note"] = "Content not available" summary = f"""File: {path} Repository: {repository.full_name} Reference: {ref} Original Size: {file_data["size_formatted"]} Content Size: {file_data["content_size"]} characters Type: {"Text" if is_text_content else "Binary"} """ if file_data["content"]: summary += ( f"Content preview (first 200 chars):\n{file_data['content'][:200]}" ) if len(file_data["content"]) > 200: summary += "..." else: summary += file_data.get("content_note", "No content preview available") data = { "summary": summary, "data": file_data, "success": True, "type": "file", "file_size": file_content.size, } # JSON 중복 처리 제거 resource_data = json.dumps(data, indent=2) metadata = { "name": f"File: {path}", "description": f"File content for {path} in {owner}/{repo}", "mime_type": "application/json", "size": len(resource_data), "uri": f"gh-file://{owner}/{repo}/{path}?ref={ref}", "source": f"https://github.com/{owner}/{repo}/blob/{ref}/{path}", "type": "file", "original_size": file_content.size, "content_size": file_data["content_size"], } return {"content": resource_data, "metadata": metadata} except Exception as e: error_data = { "error": str(e), "success": False, "uri": f"gh-file://{owner}/{repo}/{path}?ref={ref}", } resource_data = json.dumps(error_data, indent=2) metadata = { "name": f"File Error: {path}", "description": f"Error retrieving file {path}", "mime_type": "application/json", "size": len(resource_data), "uri": f"gh-file://{owner}/{repo}/{path}?ref={ref}", "error": True, } return {"content": resource_data, "metadata": metadata}

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/J-nowcow/github-MCP-practice'

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