Prompt Details
Model
(claude-4-5-sonnet)
Token size
740
Example input
[INTEGRATION]: GitHub repository
[TOOLS_NEEDED]: create_issue, list_issues, close_issue
[AUTH_TYPE]: api_key
[DEPLOYMENT]: local
Example output
## MCP Server: github-issue-manager
### Overview
This MCP server connects Claude to GitHub's REST API for issue management operations. It provides three core tools for creating, listing, and closing issues in GitHub repositories. The server uses personal access token authentication and is designed for local deployment with the Claude desktop app or API integrations.
### Tools
| Tool | Description | Parameters |
|------|-------------|------------|
| `create_issue` | Creates a new issue in a specified GitHub repository | `owner` (string), `repo` (string), `title` (string), `body` (string, optional), `labels` (array[string], optional), `assignees` (array[string], optional) |
| `list_issues` | Lists issues from a GitHub repository with filtering options | `owner` (string), `repo` (string), `state` (string: "open"\|"closed"\|"all", default: "open"), `labels` (string, optional), `per_page` (int, default: 30, max: 100) |
| `close_issue` | Closes an open issue in a GitHub repository | `owner` (string), `repo` (string), `issue_number` (int), `comment` (string, optional) |
### Implementation
```python
import os
import httpx
from mcp.server.fastmcp import FastMCP
from typing import Optional, Literal
# Initialize FastMCP server
mcp = FastMCP("github-issue-manager")
# GitHub API configuration
GITHUB_API_BASE = "https://api.github.com"
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
if not GITHUB_TOKEN:
raise ValueError("GITHUB_TOKEN environment variable is required")
# HTTP client with authentication
def get_headers():
return {
"Authorization": f"Bearer {GITHUB_TOKEN}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28",
"User-Agent": "MCP-GitHub-Issue-Manager"
}
@mcp.tool()
async def create_issue(
owner: str,
repo: str,
title: str,
body: Optional[str] = None,
labels: Optional[list[str]] = None,
assignees: Optional[list[str]] = None
) -> str:
"""
Creates a new issue in a GitHub repository.
Args:
owner: Repository owner (user or organization)
repo: Repository name
title: Issue title (required)
body: Issue description in Markdown
labels: List of label names to apply
assignees: List of GitHub usernames to assign
Returns:
JSON string with created issue details
"""
# Input validation
if not owner or not repo or not title:
return '{"error": "owner, repo, and title are required parameters"}'
if len(title) > 256:
return '{"error": "title exceeds 256 character limit"}'
url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues"
payload = {"title": title}
if body:
payload["body"] = body
if labels:
payload["labels"] = labels
if assignees:
payload["assignees"] = assignees
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(url, json=payload, headers=get_headers())
if response.status_code == 201:
issue_data = response.json()
return f'{{"success": true, "issue_number": {issue_data["number"]}, "url": "{issue_data["html_url"]}", "state": "{issue_data["state"]}"}}'
elif response.status_code == 404:
return '{"error": "Repository not found or token lacks access"}'
elif response.status_code == 403:
return '{"error": "Rate limit exceeded or insufficient permissions"}'
elif response.status_code == 422:
error_msg = response.json().get("message", "Validation failed")
return f'{{"error": "Validation error: {error_msg}"}}'
else:
return f'{{"error": "GitHub API error: {response.status_code}"}}'
except httpx.TimeoutException:
return '{"error": "Request timed out after 30 seconds"}'
except httpx.RequestError as e:
return f'{{"error": "Network error: {str(e)}"}}'
except Exception as e:
return f'{{"error": "Unexpected error: {str(e)}"}}'
@mcp.tool()
async def list_issues(
owner: str,
repo: str,
state: Literal["open", "closed", "all"] = "open",
labels: Optional[str] = None,
per_page: int = 30
) -> str:
"""
Lists issues from a GitHub repository.
Args:
owner: Repository owner
repo: Repository name
state: Filter by state (open, closed, all)
labels: Comma-separated list of label names
per_page: Results per page (1-100, default 30)
Returns:
JSON string with issue list
"""
# Input validation
if not owner or not repo:
return '{"error": "owner and repo are required parameters"}'
if per_page < 1 or per_page > 100:
return '{"error": "per_page must be between 1 and 100"}'
url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues"
params = {
"state": state,
"per_page": per_page
}
if labels:
params["labels"] = labels
try:
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.get(url, params=params, headers=get_headers())
if response.status_code == 200:
issues = response.json()
# Filter out pull requests (GitHub API includes PRs in issues endpoint)
issues = [i for i in issues if "pull_request" not in i]
simplified = [{
"number": issue["number"],
"title": issue["title"],
"state": issue["state"],
"created_at": issue["created_at"],
"updated_at": issue["updated_at"],
"url": issue["html_url"],
"labels": [label["name"] for label in issue.get("labels", [])],
"assignees": [user["login"] for user in issue.get("assignees", [])]
} for issue in issues]
import json
return json.dumps({"success": True, "count": len(simplified), "issues": simplified})
elif response.status_code == 404:
return '{"error": "Repository not found or token lacks access"}'
elif response.status_code == 403:
return '{"error": "Rate limit exceeded or insufficient permissions"}'
else:
return f'{{"error": "GitHub API error: {response.status_code}"}}'
except httpx.TimeoutException:
return '{"error": "Request timed out after 30 seconds"}'
except httpx.RequestError as e:
return f'{{"error": "Network error: {str(e)}"}}'
except Exception as e:
return f'{{"error": "Unexpected error: {str(e)}"}}'
@mcp.tool()
async def close_issue(
owner: str,
repo: str,
issue_number: int,
comment: Optional[str] = None
) -> str:
"""
Closes an open issue in a GitHub repository.
Args:
owner: Repository owner
repo: Repository name
issue_number: Issue number to close
comment: Optional closing comment
Returns:
JSON string with operation result
"""
# Input validation
if not owner or not repo or not issue_number:
return '{"error": "owner, repo, and issue_number are required parameters"}'
if issue_number < 1:
return '{"error": "issue_number must be a positive integer"}'
try:
async with httpx.AsyncClient(timeout=30.0) as client:
# Add comment if provided
if comment:
comment_url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues/{issue_number}/comments"
comment_response = await client.post(
comment_url,
json={"body": comment},
headers=get_headers()
)
if comment_response.status_code not in [200, 201]:
return f'{{"error": "Failed to add comment: {comment_response.status_code}"}}'
# Close the issue
issue_url = f"{GITHUB_API_BASE}/repos/{owner}/{repo}/issues/{issue_number}"
response = await client.patch(
issue_url,
json={"state": "closed"},
headers=get_headers()
)
if response.status_code == 200:
issue_data = response.json()
return f'{{"success": true, "issue_number": {issue_data["number"]}, "state": "{issue_data["state"]}", "url": "{issue_data["html_url"]}"}}'
elif response.status_code == 404:
return '{"error": "Issue or repository not found"}'
elif response.status_code == 403:
return '{"error": "Rate limit exceeded or insufficient permissions"}'
else:
return f'{{"error": "GitHub API error: {response.status_code}"}}'
except httpx.TimeoutException:
return '{"error": "Request timed out after 30 seconds"}'
except httpx.RequestError as e:
return f'{{"error": "Network error: {str(e)}"}}'
except Exception as e:
return f'{{"error": "Unexpected error: {str(e)}"}}'
if __name__ == "__main__":
mcp.run()
```
### Deployment
**Installation:**
```bash
# Create project directory
mkdir github-mcp-server
cd github-mcp-server
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install dependencies
pip install mcp httpx
# Save the server code as server.py
# (paste the implementation code above)
# Set your GitHub token
export GITHUB_TOKEN="ghp_your_personal_access_token_here"
```
**Claude Desktop Configuration:**
Edit your Claude configuration file:
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"github-issues": {
"command": "/path/to/github-mcp-server/venv/bin/python",
"args": ["/path/to/github-mcp-server/server.py"],
"env": {
"GITHUB_TOKEN": "ghp_your_personal_access_token_here"
}
}
}
}
```
**Testing:**
```bash
# Activate virtual environment
source venv/bin/activate
# Run the server directly (for debugging)
python server.py
# The server will start and listen for MCP protocol messages
```
### Security Considerations
1. **Token Security**
- GitHub personal access token must have `repo` scope (full repository access)
- Never commit tokens to version control
- Store token in environment variables or secure secret management
- Token should be set with minimal required permissions for production use
2. **Input Validation**
- All user inputs are validated before API calls
- String length limits enforced (title max 256 chars per GitHub limits)
- Numeric bounds checked (per_page: 1-100, issue_number > 0)
- Repository owner/name format not validated (GitHub API handles this)
3. **Rate Limiting**
- GitHub API enforces rate limits: 5,000 requests/hour for authenticated requests
- Server returns clear error messages on 403 rate limit responses
- Consider implementing client-side retry logic with exponential backoff
- No server-side rate limiting implemented (assumes single-user local deployment)
4. **Error Handling**
- Network timeouts set to 30 seconds
- All external API calls wrapped in try-catch
- Graceful degradation with descriptive error messages
- No sensitive information leaked in error responses
5. **Sandboxing**
- Server only accesses GitHub API endpoints
- No file system operations beyond environment variable reads
- No shell command execution
- Async operations prevent blocking
### Assumptions
* GitHub personal access token with `repo` scope is available and valid
* User has read/write access to target repositories
* GitHub REST API v3 endpoints remain stable (using versioned API header 2022-11-28)
* Local deployment implies single-user context (no multi-tenancy considerations)
* Standard GitHub API rate limits apply (5,000 req/hour authenticated)
* Pull requests are filtered out from `list_issues` results (GitHub includes PRs in issues endpoint)
* Network connectivity to api.github.com is available
* Python 3.10+ runtime environment
By purchasing this prompt, you agree to our terms of service
CLAUDE-4-5-SONNET
**production-ready MCP servers** that connect AI agents (Claude / GPT / Cursor) to real tools, APIs, and data sources — with security, validation, and deployment-ready code.
This is not a tutorial.
It **outputs a complete MCP server implementation** based on your inputs.
(Claude + ChatGPT Compatible)**
...more
Added over 1 month ago
