# Directory Structure
```
├── .env.template
├── .github
│   ├── ISSUE_TEMPLATE
│   │   └── bug_report.md
│   └── workflows
│       ├── ci.yml
│       └── release.yaml
├── .gitignore
├── cline_mcp_settings.json
├── memory-bank
│   ├── .clinerules
│   ├── activeContext.md
│   ├── productContext.md
│   ├── progress.md
│   ├── projectbrief.md
│   ├── systemPatterns.md
│   └── techContext.md
├── pyproject.toml
├── README.md
├── requirements.txt
├── src
│   └── mcp_github
│       ├── __init__.py
│       ├── commands
│       │   ├── __init__.py
│       │   ├── create.py
│       │   └── get.py
│       ├── github.py
│       ├── server.py
│       └── tools
│           └── __init__.py
├── tests
│   └── test_server.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.env.template:
--------------------------------------------------------------------------------
```
# GitHub authentication
GITHUB_TOKEN=your_github_personal_access_token_here
# Configuration (optional)
LOG_LEVEL=INFO  # DEBUG, INFO, WARNING, ERROR
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Environment
.env
.venv
env/
venv/
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# IDE
.idea/
.vscode/
*.swp
*.swo
*~
# Logs
*.log
.pytest_cache
```
--------------------------------------------------------------------------------
/memory-bank/.clinerules:
--------------------------------------------------------------------------------
```
# Cline Project Rules
## Project Overview
This is a Python MCP server implementation for GitHub issue management through Cline.
## Code Organization
- Python files use snake_case
- Class names use PascalCase
- Constants use UPPER_SNAKE_CASE
- Type hints required for all functions
- Docstrings following Google style
## File Structure
```
src/mcp_github/
├── server.py       # MCP server implementation
├── github.py       # GitHub API client
├── commands/       # Command pattern implementations
└── tools/         # MCP tool definitions
```
## Project Patterns
1. Use Poetry for dependency management
2. Command pattern for GitHub operations
3. Factory pattern for tool creation
4. Strategy pattern for authentication
## Development Workflow
1. Test-driven development approach
2. Black for code formatting
3. MyPy for type checking
4. Pytest for testing
## Documentation Style
- Google-style docstrings
- Clear function descriptions
- Type hints for parameters
- Return value documentation
- Error cases documented
Example:
```python
def create_issue(title: str, body: str) -> Issue:
    """Creates a new GitHub issue.
    Args:
        title: The issue title
        body: The issue description
    Returns:
        Issue: The created GitHub issue
    Raises:
        AuthenticationError: If authentication fails
        RateLimitError: If GitHub rate limit exceeded
    """
    pass
```
## Error Handling
- Custom exception hierarchy
- Descriptive error messages
- Proper error translation
- Rate limit consideration
## Testing Approach
- Unit tests required
- Integration tests for API
- Mock responses for testing
- Error case coverage
## Git Practices
- Feature branches
- Descriptive commit messages
- Max 72 chars for commit title
- Reference issues in commits
## Critical Paths
1. Authentication setup
2. GitHub API interaction
3. Rate limit handling
4. Error management
## Tool Usage Patterns
- Separate read/write tools
- Consistent response format
- Clear error reporting
- Rate limit awareness
## Project Intelligence
1. GitHub API considerations
   - Rate limits are per-hour
   - Authentication required
   - PAT scope restrictions
2. MCP Protocol notes
   - Tools for operations
   - Resources for data
   - Strict response format
3. Performance considerations
   - Cache API responses
   - Monitor rate limits
   - Async where beneficial
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# GitHub MCP Server
A Model Context Protocol (MCP) server implementation for interacting with GitHub issues through Cline.
## Features
- List GitHub issues from a repository
- Create new GitHub issues
- Error handling and validation
- Secure authentication via environment variables
## Installation
1. Clone the repository:
```bash
git clone https://github.com/timbuchinger/mcp-github.git
cd mcp-github
```
2. Install dependencies with uv:
```bash
pip install uv
uv venv
source .venv/bin/activate  # On Windows: .venv\Scripts\activate
uv pip install -r requirements.txt
```
3. Copy the environment template and configure your GitHub token:
```bash
cp .env.template .env
```
Edit `.env` and add your GitHub Personal Access Token:
```bash
GITHUB_TOKEN=your_token_here
```
To create a GitHub Personal Access Token:
1. Go to GitHub Settings -> Developer settings -> Personal access tokens
2. Generate a new token with `repo` scope
3. Copy the token and paste it in your `.env` file
## Usage
Run the MCP server:
```bash
python -m src.mcp_github.server
```
The server will start and expose two tools to Cline:
### get_issues
Get a list of issues from a GitHub repository:
```json
{
  "repo": "owner/repo"
}
```
### create_issue
Create a new issue in a GitHub repository:
```json
{
  "repo": "owner/repo",
  "title": "Issue title",
  "body": "Issue description"
}
```
## Error Handling
The server handles common errors:
- Missing GitHub token
- Invalid repository name
- Missing required parameters
- GitHub API errors
Error responses include descriptive messages to help troubleshoot issues.
## Development
The project uses uv for dependency management. To set up a development environment:
```bash
# Install all dependencies (including dev dependencies)
uv pip install -r requirements.txt
# Run tests
pytest
# Format code
black .
# Type checking
mypy .
```
--------------------------------------------------------------------------------
/src/mcp_github/__init__.py:
--------------------------------------------------------------------------------
```python
"""GitHub MCP server for Cline."""
__version__ = "0.1.0"
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
PyGithub>=2.6.0
mcp>=1.2.1
pytest>=7.4.0
black>=23.7.0
mypy>=1.5.1
```
--------------------------------------------------------------------------------
/cline_mcp_settings.json:
--------------------------------------------------------------------------------
```json
{
  "mcpServers": {
    "github": {
      "command": "uv",
      "args": [
        "--directory",
        "/fully/qualified/path/to/mcp-github/",
        "run",
        "python",
        "-m",
        "mcp_github.server"
      ],
      "env": {
        "GITHUB_TOKEN": "YOUR_GITHUB_TOKEN",
        "LOG_LEVEL": "INFO"
      },
      "disabled": true,
      "autoApprove": []
    }
  }
}
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "mcp_github"
version = "0.1.0"
description = "GitHub MCP server for Cline"
authors = [{name = "Tim"}]
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
    "PyGithub>=2.6.0",
    "mcp>=1.2.1"
]
[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "pytest-asyncio>=0.23.0",
    "black>=23.7.0",
    "mypy>=1.5.1"
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```
--------------------------------------------------------------------------------
/src/mcp_github/commands/__init__.py:
--------------------------------------------------------------------------------
```python
"""Command implementations for GitHub operations."""
from abc import ABC, abstractmethod
from typing import Any, Dict
class IssueCommand(ABC):
    """Base class for GitHub issue-related commands."""
    @abstractmethod
    def execute(self) -> Dict[str, Any]:
        """Execute the command.
        Returns:
            Dict containing the command result
        Raises:
            GitHubError: If there is an error executing the command
        """
        pass
```
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
```markdown
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
 - Python [e.g. 3.11]
 - Version [e.g. v0.1.0]
**Additional context**
Add any other context about the problem here.
```
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
```yaml
name: CI
on:
  push:
    branches: [ "main" ]
  pull_request:
    branches: [ "main" ]
jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ["3.10", "3.11", "3.12"]
    steps:
      - uses: actions/checkout@v4
      - name: Set up Python
        uses: actions/setup-python@v5
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh
      - name: Install dependencies
        run: |
          uv pip install --system -e ".[dev]"
      - name: Check formatting with Black
        run: black --check src tests
      - name: Type checking with MyPy
        run: mypy src tests
      - name: Run tests with pytest
        run: pytest tests -v
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
```yaml
name: Release
on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version number (e.g., v1.0.0)'
        required: true
        type: string
jobs:
  release:
    permissions:
      contents: write
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v3
      with:
        fetch-depth: 0
    - name: Validate version format
      run: |
        if ! [[ ${{ github.event.inputs.version }} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
          echo "Error: Version must be in format v1.0.0"
          exit 1
        fi
    - name: Create Release Tag
      run: |
        git tag ${{ github.event.inputs.version }}
        git push origin ${{ github.event.inputs.version }}
    - name: Create GitHub Release
      uses: softprops/action-gh-release@v1
      with:
        tag_name: ${{ github.event.inputs.version }}
        name: Release ${{ github.event.inputs.version }}
        draft: true
        prerelease: false
        generate_release_notes: true
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```
--------------------------------------------------------------------------------
/memory-bank/projectbrief.md:
--------------------------------------------------------------------------------
```markdown
# GitHub MCP Server Project Brief
## Overview
An MCP (Model Context Protocol) server implementation in Python that enables Cline to interact with GitHub issues. This server provides tools for reading and creating GitHub issues directly through Cline's interface.
## Core Requirements
- Integration with GitHub's REST API
- Read and list GitHub issues
- Create new GitHub issues
- Seamless integration with Cline
- Clear error handling and feedback
- Secure credential management
## Goals
1. Enable efficient GitHub issue management through Cline
2. Provide a robust and reliable MCP server implementation
3. Ensure secure handling of GitHub credentials
4. Maintain clean separation of concerns between MCP protocol and GitHub API interaction
## Success Criteria
- Successfully read existing GitHub issues
- Create new issues with proper formatting
- Handle authentication securely
- Provide clear feedback through Cline's interface
- Maintain stable connection between Cline and GitHub
## Scope
### In Scope
- GitHub issue reading
- GitHub issue creation
- Basic authentication
- Error handling
- MCP protocol implementation
### Out of Scope
- Issue comments management
- Pull request interactions
- Repository management
- GitHub project board integration
## Technical Constraints
- Python-based implementation
- MCP protocol compliance
- GitHub API rate limiting consideration
- OAuth authentication requirements
```
--------------------------------------------------------------------------------
/src/mcp_github/commands/create.py:
--------------------------------------------------------------------------------
```python
"""Command for creating GitHub issues."""
from typing import Any, Dict
from github.Issue import Issue
from ..github import GitHubClient
from . import IssueCommand
class CreateIssueCommand(IssueCommand):
    """Command for creating a new issue in a GitHub repository."""
    def __init__(self, github_client: GitHubClient, repo: str, title: str, body: str):
        """Initialize command.
        Args:
            github_client: Authenticated GitHub client
            repo: Repository name in format "owner/repo"
            title: Issue title
            body: Issue body/description
        """
        self.github_client = github_client
        self.repo = repo
        self.title = title
        self.body = body
    def _format_issue(self, issue: Issue) -> Dict[str, Any]:
        """Format a GitHub issue into a dictionary.
        Args:
            issue: GitHub issue to format
        Returns:
            Dictionary containing formatted issue data
        """
        return {
            "number": issue.number,
            "title": issue.title,
            "body": issue.body,
            "state": issue.state,
            "created_at": issue.created_at.isoformat(),
            "updated_at": issue.updated_at.isoformat(),
        }
    def execute(self) -> Dict[str, Any]:
        """Execute the create issue command.
        Returns:
            Dictionary containing created issue data
        Raises:
            GitHubError: If there is an error creating the issue
        """
        issue = self.github_client.create_issue(self.repo, self.title, self.body)
        return {"issue": self._format_issue(issue)}
```
--------------------------------------------------------------------------------
/memory-bank/progress.md:
--------------------------------------------------------------------------------
```markdown
# Progress Tracking
## Project Status: Initialization Phase
## Completed
✅ Project Planning
- Memory bank structure created
- Core documentation established
- Architecture defined
- Technical requirements documented
## In Progress
🔄 Project Setup
- Repository initialization
- Development environment configuration
- Initial package structure
## Upcoming
⏳ Foundation Implementation
- MCP server skeleton
- GitHub API client
- Authentication handling
- Basic tool implementation
## Development Roadmap
### Phase 1: Setup & Foundation
- [ ] Initialize Python project
- [ ] Configure development environment
- [ ] Set up testing framework
- [ ] Implement basic MCP server
- [ ] Create GitHub API client
### Phase 2: Core Features
- [ ] Implement authentication
- [ ] Add rate limiting
- [ ] Create issue reading tool
- [ ] Create issue creation tool
- [ ] Add error handling
### Phase 3: Enhancement
- [ ] Implement caching
- [ ] Add comprehensive testing
- [ ] Improve error messages
- [ ] Optimize performance
- [ ] Add documentation
## Known Issues
*(No issues yet - project in initialization phase)*
## Testing Status
- Unit Tests: Not started
- Integration Tests: Not started
- End-to-End Tests: Not started
- Coverage: 0%
## Documentation Status
✅ Memory Bank
- Project brief
- Product context
- System patterns
- Technical context
- Active context
- Progress tracking
⏳ Code Documentation
- [ ] README
- [ ] API documentation
- [ ] Setup guide
- [ ] Usage examples
## Metrics
*(To be tracked once implementation begins)*
- Code coverage
- API response times
- Error rates
- Rate limit usage
## Notes
Initial setup phase in progress. Focus on establishing solid foundation before moving to implementation.
```
--------------------------------------------------------------------------------
/src/mcp_github/commands/get.py:
--------------------------------------------------------------------------------
```python
"""Command for retrieving GitHub issues."""
from typing import Any, Dict, List, Optional
from github.Issue import Issue
from ..github import GitHubClient
from . import IssueCommand
class GetIssuesCommand(IssueCommand):
    """Command for retrieving issues from a GitHub repository."""
    def __init__(
        self,
        github_client: GitHubClient,
        repo: str,
        state: str = "open",
        query: Optional[str] = None,
    ):
        """Initialize command.
        Args:
            github_client: Authenticated GitHub client
            repo: Repository name in format "owner/repo"
            state: State of issues to retrieve ("open", "closed", or "all")
            query: Optional search query to filter issues
        """
        self.github_client = github_client
        self.repo = repo
        self.state = state
        self.query = query
    def _format_issue(self, issue: Issue) -> Dict[str, Any]:
        """Format a GitHub issue into a dictionary.
        Args:
            issue: GitHub issue to format
        Returns:
            Dictionary containing formatted issue data
        """
        return {
            "number": issue.number,
            "title": issue.title,
            "body": issue.body,
            "state": issue.state,
            "created_at": issue.created_at.isoformat(),
            "updated_at": issue.updated_at.isoformat(),
        }
    def execute(self) -> Dict[str, Any]:
        """Execute the get issues command.
        Returns:
            Dictionary containing list of issues
        Raises:
            GitHubError: If there is an error retrieving the issues
        """
        issues = self.github_client.get_issues(self.repo, self.state, self.query)
        return {"issues": [self._format_issue(issue) for issue in issues]}
```
--------------------------------------------------------------------------------
/memory-bank/productContext.md:
--------------------------------------------------------------------------------
```markdown
# Product Context
## Purpose
The GitHub MCP server exists to bridge the gap between Cline's AI capabilities and GitHub's issue tracking system. It enables developers to manage GitHub issues directly through Cline's interface, streamlining the development workflow and enhancing productivity.
## Problems Solved
1. **Context Switching**
   - Eliminates need to switch between Cline and GitHub web interface
   - Maintains flow of development conversations
   
2. **Issue Management Efficiency**
   - Direct issue creation from development discussions
   - Quick access to existing issues for reference
   
3. **Integration Cohesion**
   - Seamless connection between Cline and GitHub
   - Consistent experience within Cline's interface
## Intended Workflow
1. User discusses development tasks with Cline
2. Cline can immediately check existing issues or create new ones
3. Issue creation/reading happens without leaving the conversation
4. Smooth transition between discussion and issue management
## User Experience Goals
- **Simplicity**: Straightforward issue management within Cline
- **Speed**: Quick access to GitHub functionality
- **Reliability**: Consistent and dependable operation
- **Security**: Safe handling of GitHub credentials
- **Clarity**: Clear feedback about operations and status
## Integration Points
1. **Cline Interface**
   - Natural language processing of issue-related requests
   - Structured response formatting
   
2. **GitHub API**
   - Authentication and authorization
   - Issue creation and retrieval
   
3. **MCP Protocol**
   - Standardized communication format
   - Tool and resource exposure
## Success Indicators
- Reduced context switching for developers
- Increased efficiency in issue management
- Positive user feedback on integration
- Reliable and consistent operation
```
--------------------------------------------------------------------------------
/memory-bank/activeContext.md:
--------------------------------------------------------------------------------
```markdown
# Active Context
## Current Phase
Initial project setup and foundation building
## Recent Changes
- Created memory bank structure
- Defined project requirements and scope
- Established technical architecture
- Documented development setup
## Current Focus
1. Project Infrastructure
   - Repository setup
   - Development environment configuration
   - Initial package structure
2. Core Implementation
   - MCP server skeleton
   - GitHub API client foundation
   - Basic authentication flow
3. Tool Design
   - Issue reading implementation
   - Issue creation implementation
   - Error handling framework
## Active Decisions
### Authentication Strategy
- Using Personal Access Token (PAT)
- Secure storage via environment variables
- Token scope limited to issue management
### API Integration
- PyGithub as primary GitHub API client
- Async operations where beneficial
- Caching for rate limit management
### Tool Structure
- Separate tools for read and create operations
- Consistent response formatting
- Clear error messages
## Immediate Priorities
1. Set up basic project structure
   - Initialize Python package
   - Configure development tools
   - Set up testing framework
2. Implement MCP server foundation
   - Basic server setup
   - Tool registration
   - Error handling
3. Create GitHub API client
   - Authentication implementation
   - Basic API operations
   - Rate limit handling
## Pending Considerations
- Cache implementation details
- Rate limit strategies
- Error reporting format
- Testing approach
## Next Steps
1. Create initial project structure
2. Set up development environment
3. Implement basic MCP server
4. Add GitHub authentication
5. Create first tool implementation
## Open Questions
- Cache duration for API responses
- Error message format preferences
- Rate limit handling strategy
- Testing coverage requirements
```
--------------------------------------------------------------------------------
/memory-bank/systemPatterns.md:
--------------------------------------------------------------------------------
```markdown
# System Patterns
## Architecture Overview
```mermaid
graph TD
    C[Cline] --- M[MCP Protocol Layer]
    M --- S[Server Core]
    S --- GH[GitHub API Client]
    GH --- API[GitHub REST API]
```
## Core Components
### 1. MCP Protocol Layer
- Handles communication with Cline
- Implements MCP server protocol
- Exposes tools and resources
- Manages request/response lifecycle
### 2. Server Core
- Central coordination layer
- Authentication management
- Error handling and logging
- Request validation
### 3. GitHub API Client
- GitHub REST API interaction
- Rate limiting management
- Response parsing
- Error translation
## Design Patterns
### 1. Command Pattern
- Each GitHub operation encapsulated as a command
- Standardized execution flow
- Consistent error handling
```python
class IssueCommand:
    def execute(self):
        pass
```
### 2. Factory Pattern
- Tool and resource creation
- Command instantiation
- Response formatting
```python
class ToolFactory:
    def create_tool(self, name: str):
        pass
```
### 3. Strategy Pattern
- Authentication strategies
- Response formatting
- Rate limiting approaches
```python
class AuthStrategy:
    def authenticate(self):
        pass
```
## Component Relationships
### Tool Implementation
```mermaid
graph LR
    T[Tool Request] --> V[Validator]
    V --> C[Command]
    C --> G[GitHub API]
    G --> R[Response]
    R --> F[Formatter]
    F --> TR[Tool Response]
```
### Resource Implementation
```mermaid
graph LR
    R[Resource Request] --> C[Cache Check]
    C --> |Miss| G[GitHub API]
    C --> |Hit| RC[Return Cached]
    G --> S[Store Cache]
    S --> RR[Return Response]
```
## Error Handling
- Hierarchical error types
- Consistent error translation
- Clear error messages
- Rate limit handling
## Data Flow
1. Request received through MCP
2. Request validated
3. Command created and executed
4. Response formatted
5. Response returned through MCP
## Security Patterns
- Credential encryption
- Token-based authentication
- Secure storage practices
- Request validation
## Testing Patterns
- Unit tests per component
- Integration tests for GitHub API
- Mock MCP client for testing
- Error scenario coverage
```
--------------------------------------------------------------------------------
/src/mcp_github/github.py:
--------------------------------------------------------------------------------
```python
"""GitHub API client implementation."""
from typing import List, Optional
from github import Github, GithubException
from github.Issue import Issue
class GitHubError(Exception):
    """Custom exception for GitHub API errors."""
    def __init__(self, message: str, status: Optional[int] = None):
        self.status = status
        super().__init__(message)
class GitHubClient:
    """Client for interacting with GitHub API."""
    def __init__(self, token: str):
        """Initialize GitHub client with authentication token.
        Args:
            token: GitHub personal access token
        """
        self.api = Github(token)
    def get_issues(
        self, repo: str, state: str = "open", query: Optional[str] = None
    ) -> List[Issue]:
        """Get list of issues from a repository.
        Args:
            repo: Repository name in format "owner/repo"
            state: State of issues to retrieve ("open", "closed", or "all")
            query: Optional search query to filter issues
        Returns:
            List of GitHub issues
        Raises:
            GitHubError: If there is an error accessing the issues
        """
        try:
            repository = self.api.get_repo(repo)
            if query:
                query_str = f"repo:{repo} {query} state:{state}"
                return list(self.api.search_issues(query_str))
            return list(repository.get_issues(state=state))
        except GithubException as e:
            raise GitHubError(str(e.data.get("message", "Unknown error")), e.status)
    def create_issue(self, repo: str, title: str, body: str) -> Issue:
        """Create a new issue in a repository.
        Args:
            repo: Repository name in format "owner/repo"
            title: Issue title
            body: Issue body/description
        Returns:
            Created GitHub issue
        Raises:
            GitHubError: If there is an error creating the issue
        """
        try:
            repository = self.api.get_repo(repo)
            return repository.create_issue(title=title, body=body)
        except GithubException as e:
            raise GitHubError(str(e.data.get("message", "Unknown error")), e.status)
```
--------------------------------------------------------------------------------
/memory-bank/techContext.md:
--------------------------------------------------------------------------------
```markdown
# Technical Context
## Technologies
### Core Stack
- **Python 3.9+**
  - Modern Python features
  - Type hinting support
  - Async capabilities
### Dependencies
1. **MCP SDK**
   - `@modelcontextprotocol/sdk`
   - Server implementation
   - Protocol handling
2. **GitHub API**
   - PyGithub package
   - REST API interaction
   - Rate limit handling
3. **Development Tools**
   - uv (dependency management)
   - Black (code formatting)
   - MyPy (static type checking)
   - Pytest (testing framework)
## Development Setup
### Environment Requirements
```bash
# Python version
Python 3.9+
# Required system packages
git
python3-venv
python3-pip
# Virtual environment
python -m venv .venv
source .venv/bin/activate
```
### Project Structure
```
mcp-github/
├── pyproject.toml        # Project metadata and dependencies
├── poetry.lock          # Locked dependencies
├── src/
│   └── mcp_github/     # Main package
│       ├── __init__.py
│       ├── server.py    # MCP server implementation
│       ├── github.py    # GitHub API client
│       ├── commands/    # Command implementations
│       └── tools/       # MCP tool definitions
├── tests/              # Test suite
└── docs/              # Documentation
```
## Technical Constraints
### 1. Authentication
- GitHub Personal Access Token required
- Secure token storage
- Token scope limitations
### 2. Rate Limiting
- GitHub API rate limits
- Cache implementation
- Rate limit handling
### 3. Dependencies
- Minimum dependency footprint
- Maintained packages only
- Security considerations
### 4. Performance
- Async operations where beneficial
- Response caching
- Efficient API usage
## Configuration
### Environment Variables
```bash
GITHUB_TOKEN=            # GitHub Personal Access Token
GITHUB_API_URL=         # GitHub API URL (optional)
LOG_LEVEL=             # Logging level (optional)
CACHE_TTL=            # Cache time-to-live (optional)
```
### Development Configuration
```toml
[project]
name = "mcp-github"
version = "0.1.0"
description = "GitHub MCP server for Cline"
requires-python = ">=3.10"
dependencies = [
    "PyGithub>=2.6.0",
    "mcp>=1.2.1"
]
[project.optional-dependencies]
dev = [
    "pytest>=7.4.0",
    "black>=23.7.0",
    "mypy>=1.5.1"
]
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```
## Testing Strategy
- Unit tests for each component
- Integration tests with GitHub API
- Mock MCP client
- GitHub API response mocking
- Comprehensive error testing
## Error Handling
- Custom exception hierarchy
- Detailed error messages
- Error translation to MCP format
- Logging for debugging
## Security Considerations
- Token validation
- Secure credential storage
- Input validation
- Rate limit protection
- Error message sanitization
```
--------------------------------------------------------------------------------
/src/mcp_github/tools/__init__.py:
--------------------------------------------------------------------------------
```python
"""Tool definitions for GitHub MCP server."""
import json
from typing import Any, Dict
from ..commands.create import CreateIssueCommand
from ..commands.get import GetIssuesCommand
from ..github import GitHubClient, GitHubError
def get_github_client() -> GitHubClient:
    """Get an authenticated GitHub client from environment variables.
    Returns:
        Authenticated GitHub client
    Raises:
        GitHubError: If GitHub token is not configured
    """
    import os
    token = os.getenv("GITHUB_TOKEN")
    if not token:
        raise GitHubError("GITHUB_TOKEN environment variable not set")
    return GitHubClient(token)
def format_error(error: GitHubError) -> Dict[str, Any]:
    """Format a GitHub error for MCP response.
    Args:
        error: GitHub error to format
    Returns:
        Dictionary containing error details
    """
    return {
        "content": [
            {
                "type": "text",
                "text": str(error),
            }
        ],
        "isError": True,
    }
async def get_issues(arguments: Dict[str, Any]) -> Dict[str, Any]:
    """MCP tool implementation for getting GitHub issues.
    Args:
        arguments: Tool arguments containing repository
    Returns:
        Tool response containing issues or error
    """
    try:
        repo = arguments.get("repo")
        if not repo:
            raise GitHubError("Repository not specified")
        client = get_github_client()
        command = GetIssuesCommand(client, repo)
        result = command.execute()
        return {
            "content": [
                {
                    "type": "text",
                    "text": json.dumps(result, indent=2),
                }
            ],
        }
    except GitHubError as e:
        return format_error(e)
async def create_issue(arguments: Dict[str, Any]) -> Dict[str, Any]:
    """MCP tool implementation for creating GitHub issues.
    Args:
        arguments: Tool arguments containing repository, title, and body
    Returns:
        Tool response containing created issue or error
    """
    try:
        repo = arguments.get("repo")
        title = arguments.get("title")
        body = arguments.get("body", "")
        if not repo:
            raise GitHubError("Repository not specified")
        if not title:
            raise GitHubError("Issue title not specified")
        client = get_github_client()
        command = CreateIssueCommand(client, repo, title, body)
        result = command.execute()
        return {
            "content": [
                {
                    "type": "text",
                    "text": json.dumps(result, indent=2),
                }
            ],
        }
    except GitHubError as e:
        return format_error(e)
```
--------------------------------------------------------------------------------
/src/mcp_github/server.py:
--------------------------------------------------------------------------------
```python
"""MCP server implementation for GitHub integration."""
import logging
import sys
from typing import Any, Awaitable, Callable, Dict
import mcp.types as types
from mcp.server import Server
from mcp.shared.exceptions import McpError
from mcp.types import (
    INTERNAL_ERROR,
    CallToolRequestParams,
    ErrorData,
    ListResourcesRequest,
    ListResourceTemplatesRequest,
    ListToolsRequest,
    TextContent,
    Tool,
)
from .tools import create_issue, get_issues
# Initialize server at module level
server = Server("github")
logging.basicConfig(stream=sys.stderr, level=logging.INFO)
logger = logging.getLogger("mcp_github")
# Set up request handlers
@server.list_resources()
async def list_resources():
    """List available resources (none for this server)."""
    return []
@server.list_resource_templates()
async def list_resource_templates():
    """List available resource templates (none for this server)."""
    return []
@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="get_issues",
            description="Get list of issues from a GitHub repository",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository name in format owner/repo",
                    },
                },
                "required": ["repo"],
            },
        ),
        Tool(
            name="create_issue",
            description="Create a new issue in a GitHub repository",
            inputSchema={
                "type": "object",
                "properties": {
                    "repo": {
                        "type": "string",
                        "description": "Repository name in format owner/repo",
                    },
                    "title": {
                        "type": "string",
                        "description": "Issue title",
                    },
                    "body": {
                        "type": "string",
                        "description": "Issue body/description",
                    },
                },
                "required": ["repo", "title"],
            },
        ),
    ]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict | None) -> list[TextContent]:
    """Handle tool operations."""
    logger.info(f"Handling tool {name} with arguments {arguments}")
    if not arguments:
        arguments = {}
    try:
        tool_handlers: Dict[str, Callable[[dict], Awaitable[Any]]] = {
            "get_issues": get_issues,
            "create_issue": create_issue,
        }
        handler = tool_handlers.get(name)
        if not handler:
            raise ValueError(f"Unknown tool: {name}")
        result = await handler(arguments)
        return [TextContent(type="text", text=str(result))]
    except Exception as e:
        logger.error(f"Error handling tool {name}: {e}")
        logger.exception(e)
        raise McpError(
            ErrorData(
                code=INTERNAL_ERROR,
                message=f"Error handling tool {name}: {e}",
            )
        )
async def run_server():
    """Run the MCP server on stdio transport."""
    from mcp.server.stdio import stdio_server
    async with stdio_server() as streams:
        await server.run(streams[0], streams[1], server.create_initialization_options())
def main():
    """Run the GitHub MCP server."""
    import asyncio
    logger.info("Starting GitHub MCP server")
    asyncio.run(run_server())
if __name__ == "__main__":
    main()
```
--------------------------------------------------------------------------------
/tests/test_server.py:
--------------------------------------------------------------------------------
```python
"""Tests for the MCP GitHub server implementation."""
import asyncio
from datetime import datetime
from io import StringIO
from unittest.mock import AsyncMock, Mock, PropertyMock, patch
import pytest
from mcp.shared.exceptions import McpError
from mcp.types import TextContent
from mcp_github.github import GitHubError
from mcp_github.server import (
    handle_call_tool,
    list_resource_templates,
    list_resources,
    list_tools,
    server,
)
@pytest.fixture
def mock_transport():
    """Create mock input/output streams for testing."""
    input_stream = StringIO()
    output_stream = StringIO()
    return input_stream, output_stream
@pytest.mark.asyncio
async def test_list_tools():
    """Test that list_tools returns the expected tool definitions."""
    tools = await list_tools()
    assert isinstance(tools, list)
    assert len(tools) == 2  # get_issues and create_issue
    # Verify get_issues tool
    get_issues = next(t for t in tools if t.name == "get_issues")
    assert get_issues.description == "Get list of issues from a GitHub repository"
    assert get_issues.inputSchema["required"] == ["repo"]
    # Verify create_issue tool
    create_issue = next(t for t in tools if t.name == "create_issue")
    assert create_issue.description == "Create a new issue in a GitHub repository"
    assert set(create_issue.inputSchema["required"]) == {"repo", "title"}
@pytest.mark.asyncio
async def test_list_resources():
    """Test that list_resources returns an empty list."""
    resources = await list_resources()
    assert isinstance(resources, list)
    assert len(resources) == 0
@pytest.mark.asyncio
async def test_list_resource_templates():
    """Test that list_resource_templates returns an empty list."""
    templates = await list_resource_templates()
    assert isinstance(templates, list)
    assert len(templates) == 0
@pytest.mark.asyncio
@patch("mcp_github.tools.get_github_client")
async def test_get_issues_tool(mock_get_github_client):
    """Test the get_issues tool with valid arguments."""
    # Create a mock GitHub client
    mock_client = Mock()
    # Create a mock Issue object
    mock_issue = Mock()
    type(mock_issue).number = PropertyMock(return_value=1)
    type(mock_issue).title = PropertyMock(return_value="Test Issue")
    type(mock_issue).body = PropertyMock(return_value="Test body")
    type(mock_issue).state = PropertyMock(return_value="open")
    type(mock_issue).created_at = PropertyMock(return_value=datetime(2025, 2, 18))
    type(mock_issue).updated_at = PropertyMock(return_value=datetime(2025, 2, 18))
    mock_client.get_issues.return_value = [mock_issue]
    mock_get_github_client.return_value = mock_client
    response = await handle_call_tool("get_issues", {"repo": "owner/repo"})
    assert isinstance(response, list)
    assert len(response) == 1
    assert response[0].type == "text"
    assert "Test Issue" in response[0].text
    mock_client.get_issues.assert_called_once_with("owner/repo", "open", None)
@pytest.mark.asyncio
@patch("mcp_github.tools.get_github_client")
async def test_create_issue_tool(mock_get_github_client):
    """Test the create_issue tool with valid arguments."""
    # Create a mock GitHub client
    mock_client = Mock()
    # Create a mock Issue object
    mock_issue = Mock()
    type(mock_issue).number = PropertyMock(return_value=1)
    type(mock_issue).title = PropertyMock(return_value="Test Issue")
    type(mock_issue).body = PropertyMock(return_value="Test Description")
    type(mock_issue).state = PropertyMock(return_value="open")
    type(mock_issue).created_at = PropertyMock(return_value=datetime(2025, 2, 18))
    type(mock_issue).updated_at = PropertyMock(return_value=datetime(2025, 2, 18))
    mock_client.create_issue.return_value = mock_issue
    mock_get_github_client.return_value = mock_client
    response = await handle_call_tool(
        "create_issue",
        {
            "repo": "owner/repo",
            "title": "Test Issue",
            "body": "Test Description",
        },
    )
    assert isinstance(response, list)
    assert len(response) == 1
    assert response[0].type == "text"
    mock_client.create_issue.assert_called_once_with(
        "owner/repo", "Test Issue", "Test Description"
    )
@pytest.mark.asyncio
async def test_invalid_tool_name():
    """Test that calling an invalid tool name raises an error."""
    with pytest.raises(McpError) as exc_info:
        await handle_call_tool("invalid_tool", {})
    assert "Unknown tool: invalid_tool" in str(exc_info.value)
@pytest.mark.asyncio
@patch("mcp_github.tools.get_github_client")
async def test_missing_required_arguments(mock_get_github_client):
    """Test that calling a tool with missing required arguments raises an error."""
    response = await handle_call_tool(
        "create_issue", {"body": "Missing required repo and title"}
    )
    assert len(response) == 1
    assert response[0].type == "text"
    assert "Repository not specified" in response[0].text
@pytest.mark.asyncio
@patch("mcp_github.tools.get_github_client")
async def test_null_arguments(mock_get_github_client):
    """Test that calling a tool with null arguments doesn't crash."""
    response = await handle_call_tool("get_issues", None)
    assert len(response) == 1
    assert response[0].type == "text"
    assert "Repository not specified" in response[0].text
```