# server.py
from fastmcp import FastMCP
import httpx
import os
mcp = FastMCP("GitHub Tools")
# In a real app, get the token from the request context
# FastMCP handles OAuth token injection per-request
GITHUB_API = "https://api.github.com"
@mcp.tool()
async def create_issue(
repo: str,
title: str,
body: str = "",
labels: list[str] = []
) -> dict:
"""
Create a new GitHub issue in a repository.
Use when the user wants to report a bug, request a feature, or track work
in a specific GitHub repository.
Args:
repo: Repository in 'owner/repo' format, e.g. 'acme/todo-app'
title: Short, descriptive issue title
body: Detailed description of the issue (optional)
labels: List of label names to apply, e.g. ['bug', 'high-priority']
"""
async with httpx.AsyncClient() as client:
resp = await client.post(
f"{GITHUB_API}/repos/{repo}/issues",
json={"title": title, "body": body, "labels": labels},
headers={"Authorization": f"Bearer {get_token()}"}
)
resp.raise_for_status()
data = resp.json()
return {
"issue_number": data["number"],
"url": data["html_url"],
"title": data["title"],
"status": "created"
}
@mcp.tool()
async def list_issues(
repo: str,
state: str = "open",
limit: int = 10
) -> list:
"""
List issues in a GitHub repository.
Use when the user wants to see open bugs, check pending work, or review
issues in a repository.
Args:
repo: Repository in 'owner/repo' format
state: Filter by issue state: 'open', 'closed', or 'all'. Default: 'open'
limit: Maximum number of issues to return (1–50). Default: 10
"""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{GITHUB_API}/repos/{repo}/issues",
params={"state": state, "per_page": min(limit, 50)},
headers={"Authorization": f"Bearer {get_token()}"}
)
resp.raise_for_status()
issues = resp.json()
return [
{"number": i["number"], "title": i["title"], "url": i["html_url"]}
for i in issues
]
@mcp.tool()
async def search_repos(query: str, limit: int = 5) -> list:
"""
Search for GitHub repositories matching a query.
Use when the user wants to find repositories by name, topic, or description.
Args:
query: Search keywords, e.g. 'fastapi authentication python'
limit: Number of results to return (1–20). Default: 5
"""
async with httpx.AsyncClient() as client:
resp = await client.get(
f"{GITHUB_API}/search/repositories",
params={"q": query, "per_page": min(limit, 20)},
headers={"Authorization": f"Bearer {get_token()}"}
)
resp.raise_for_status()
return [
{
"name": r["full_name"],
"description": r["description"],
"stars": r["stargazers_count"],
"url": r["html_url"]
}
for r in resp.json()["items"]
]
def get_token():
# In production: extract from request context (Orceum injects it)
return os.environ.get("GITHUB_TOKEN", "")
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8080)