Skip to main content
Glama
server.pyβ€’10.3 kB
#!/usr/bin/env python3 """ GitHub MCP Server A FastMCP server that provides access to GitHub repositories and data. """ import asyncio import json import os from typing import Any, Dict, List, Optional import httpx from fastmcp import FastMCP # Create the FastMCP server mcp = FastMCP("GitHub MCP Server") # GitHub API configuration GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") or os.getenv("GITHUB_API_KEY") or "demo" GITHUB_API_BASE = "https://api.github.com" def get_headers(api_key=None): """Get headers for GitHub API requests.""" headers = { "Accept": "application/vnd.github.v3+json", "User-Agent": "GitHub-MCP-Server" } # Use provided API key or fallback to environment variable token = api_key or GITHUB_TOKEN if token and token != "demo": headers["Authorization"] = f"Bearer {token}" return headers @mcp.tool() def get_repos(username: str = None, limit: int = 10) -> str: """Get repositories for a GitHub user. Args: username: GitHub username (optional); if not provided, returns authenticated user's repos limit: Number of repositories to return (default: 10, max: 30) Returns: JSON string with repository data """ try: limit = min(max(limit, 1), 30) # Clamp between 1 and 30 token = GITHUB_TOKEN if not token or token == "demo": return json.dumps({"error": "GitHub token not configured. Please set GITHUB_TOKEN environment variable."}, indent=2) params = { "sort": "updated", "per_page": limit } # Determine endpoint based on username parameter if username and username.strip(): # Default to public-only endpoint for arbitrary users endpoint = f"{GITHUB_API_BASE}/users/{username}/repos" # If the provided username matches the authenticated user, use /user/repos to include private repos try: me_response = httpx.get( f"{GITHUB_API_BASE}/user", headers=get_headers(token), timeout=10.0 ) me_response.raise_for_status() me = me_response.json() auth_login = (me.get("login") or "").strip() if auth_login and auth_login.lower() == username.strip().lower(): endpoint = f"{GITHUB_API_BASE}/user/repos" except httpx.RequestError: # Fall back to public endpoint if we cannot determine the authenticated user pass except httpx.HTTPStatusError: # Fall back to public endpoint if token is invalid/insufficient pass else: # Get repos for authenticated user (public + private) # This happens when username is None or empty string endpoint = f"{GITHUB_API_BASE}/user/repos" response = httpx.get( endpoint, headers=get_headers(token), params=params, timeout=10.0 ) response.raise_for_status() repos = response.json() formatted_repos = [] for repo in repos: formatted_repos.append({ "name": repo["name"], "full_name": repo["full_name"], "description": repo.get("description", ""), "url": repo["html_url"], "stars": repo["stargazers_count"], "forks": repo["forks_count"], "language": repo.get("language", ""), "created_at": repo["created_at"], "updated_at": repo["updated_at"], "private": repo["private"] }) return json.dumps(formatted_repos, indent=2) except httpx.RequestError as e: return json.dumps({"error": f"Request failed: {str(e)}"}, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) @mcp.tool() def get_issues(owner: str, repo: str, state: str = "open", limit: int = 10) -> str: """Get issues for a GitHub repository. Args: owner: Repository owner repo: Repository name state: Issue state: open, closed, or all (default: open) limit: Number of issues to return (default: 10, max: 30) Returns: JSON string with issues data """ try: limit = min(max(limit, 1), 30) # Clamp between 1 and 30 token = GITHUB_TOKEN if not token or token == "demo": return json.dumps({"error": "GitHub token not configured. Please set GITHUB_TOKEN environment variable."}, indent=2) params = { "state": state, "per_page": limit, "sort": "updated" } response = httpx.get( f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues", headers=get_headers(token), params=params, timeout=10.0 ) response.raise_for_status() issues = response.json() formatted_issues = [] for issue in issues: # Skip pull requests (they appear in issues API) if "pull_request" in issue: continue formatted_issues.append({ "number": issue["number"], "title": issue["title"], "body": issue.get("body", ""), "state": issue["state"], "url": issue["html_url"], "user": issue["user"]["login"], "labels": [label["name"] for label in issue["labels"]], "created_at": issue["created_at"], "updated_at": issue["updated_at"], "comments": issue["comments"] }) return json.dumps(formatted_issues, indent=2) except httpx.RequestError as e: return json.dumps({"error": f"Request failed: {str(e)}"}, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) @mcp.tool() def get_pull_requests(owner: str, repo: str, state: str = "open", limit: int = 10) -> str: """Get pull requests for a GitHub repository. Args: owner: Repository owner repo: Repository name state: PR state: open, closed, or all (default: open) limit: Number of PRs to return (default: 10, max: 30) Returns: JSON string with pull requests data """ try: limit = min(max(limit, 1), 30) # Clamp between 1 and 30 token = GITHUB_TOKEN if not token or token == "demo": return json.dumps({"error": "GitHub token not configured. Please set GITHUB_TOKEN environment variable."}, indent=2) params = { "state": state, "per_page": limit, "sort": "updated" } response = httpx.get( f"{GITHUB_API_BASE}/repos/{owner}/{repo}/pulls", headers=get_headers(token), params=params, timeout=10.0 ) response.raise_for_status() prs = response.json() formatted_prs = [] for pr in prs: formatted_prs.append({ "number": pr["number"], "title": pr["title"], "body": pr.get("body", ""), "state": pr["state"], "url": pr["html_url"], "user": pr["user"]["login"], "head": pr["head"]["ref"], "base": pr["base"]["ref"], "created_at": pr["created_at"], "updated_at": pr["updated_at"], "draft": pr["draft"], "mergeable": pr.get("mergeable") }) return json.dumps(formatted_prs, indent=2) except httpx.RequestError as e: return json.dumps({"error": f"Request failed: {str(e)}"}, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) @mcp.tool() def search_code(query: str, language: str = "", limit: int = 10) -> str: """Search for code on GitHub. Args: query: Search query language: Programming language filter (optional) limit: Number of results to return (default: 10, max: 20) Returns: JSON string with search results """ try: limit = min(max(limit, 1), 20) # Clamp between 1 and 20 token = GITHUB_TOKEN if not token or token == "demo": return json.dumps({"error": "GitHub token not configured. Please set GITHUB_TOKEN environment variable."}, indent=2) search_query = query if language: search_query += f" language:{language}" params = { "q": search_query, "per_page": limit, "sort": "indexed" } response = httpx.get( f"{GITHUB_API_BASE}/search/code", headers=get_headers(token), params=params, timeout=10.0 ) response.raise_for_status() data = response.json() results = [] for item in data.get("items", []): results.append({ "name": item["name"], "path": item["path"], "repository": item["repository"]["full_name"], "url": item["html_url"], "language": item.get("language", ""), "score": item["score"] }) search_results = { "total_count": data["total_count"], "results": results } return json.dumps(search_results, indent=2) except httpx.RequestError as e: return json.dumps({"error": f"Request failed: {str(e)}"}, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) if __name__ == "__main__": port = int(os.environ.get("PORT", 8000)) mcp.run( transport="http", host="0.0.0.0", port=port, stateless_http=True )

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/akarnik23/mcp-github'

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