#
tokens: 13251/50000 24/24 files
lines: on (toggle) GitHub
raw markdown copy reset
# 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:
--------------------------------------------------------------------------------

```
1 | # GitHub authentication
2 | GITHUB_TOKEN=your_github_personal_access_token_here
3 | 
4 | # Configuration (optional)
5 | LOG_LEVEL=INFO  # DEBUG, INFO, WARNING, ERROR
6 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Environment
 2 | .env
 3 | .venv
 4 | env/
 5 | venv/
 6 | 
 7 | # Python
 8 | __pycache__/
 9 | *.py[cod]
10 | *$py.class
11 | *.so
12 | .Python
13 | build/
14 | develop-eggs/
15 | dist/
16 | downloads/
17 | eggs/
18 | .eggs/
19 | lib/
20 | lib64/
21 | parts/
22 | sdist/
23 | var/
24 | wheels/
25 | *.egg-info/
26 | .installed.cfg
27 | *.egg
28 | 
29 | # IDE
30 | .idea/
31 | .vscode/
32 | *.swp
33 | *.swo
34 | *~
35 | 
36 | # Logs
37 | *.log
38 | .pytest_cache
39 | 
```

--------------------------------------------------------------------------------
/memory-bank/.clinerules:
--------------------------------------------------------------------------------

```
  1 | # Cline Project Rules
  2 | 
  3 | ## Project Overview
  4 | This is a Python MCP server implementation for GitHub issue management through Cline.
  5 | 
  6 | ## Code Organization
  7 | - Python files use snake_case
  8 | - Class names use PascalCase
  9 | - Constants use UPPER_SNAKE_CASE
 10 | - Type hints required for all functions
 11 | - Docstrings following Google style
 12 | 
 13 | ## File Structure
 14 | ```
 15 | src/mcp_github/
 16 | ├── server.py       # MCP server implementation
 17 | ├── github.py       # GitHub API client
 18 | ├── commands/       # Command pattern implementations
 19 | └── tools/         # MCP tool definitions
 20 | ```
 21 | 
 22 | ## Project Patterns
 23 | 1. Use Poetry for dependency management
 24 | 2. Command pattern for GitHub operations
 25 | 3. Factory pattern for tool creation
 26 | 4. Strategy pattern for authentication
 27 | 
 28 | ## Development Workflow
 29 | 1. Test-driven development approach
 30 | 2. Black for code formatting
 31 | 3. MyPy for type checking
 32 | 4. Pytest for testing
 33 | 
 34 | ## Documentation Style
 35 | - Google-style docstrings
 36 | - Clear function descriptions
 37 | - Type hints for parameters
 38 | - Return value documentation
 39 | - Error cases documented
 40 | 
 41 | Example:
 42 | ```python
 43 | def create_issue(title: str, body: str) -> Issue:
 44 |     """Creates a new GitHub issue.
 45 | 
 46 |     Args:
 47 |         title: The issue title
 48 |         body: The issue description
 49 | 
 50 |     Returns:
 51 |         Issue: The created GitHub issue
 52 | 
 53 |     Raises:
 54 |         AuthenticationError: If authentication fails
 55 |         RateLimitError: If GitHub rate limit exceeded
 56 |     """
 57 |     pass
 58 | ```
 59 | 
 60 | ## Error Handling
 61 | - Custom exception hierarchy
 62 | - Descriptive error messages
 63 | - Proper error translation
 64 | - Rate limit consideration
 65 | 
 66 | ## Testing Approach
 67 | - Unit tests required
 68 | - Integration tests for API
 69 | - Mock responses for testing
 70 | - Error case coverage
 71 | 
 72 | ## Git Practices
 73 | - Feature branches
 74 | - Descriptive commit messages
 75 | - Max 72 chars for commit title
 76 | - Reference issues in commits
 77 | 
 78 | ## Critical Paths
 79 | 1. Authentication setup
 80 | 2. GitHub API interaction
 81 | 3. Rate limit handling
 82 | 4. Error management
 83 | 
 84 | ## Tool Usage Patterns
 85 | - Separate read/write tools
 86 | - Consistent response format
 87 | - Clear error reporting
 88 | - Rate limit awareness
 89 | 
 90 | ## Project Intelligence
 91 | 1. GitHub API considerations
 92 |    - Rate limits are per-hour
 93 |    - Authentication required
 94 |    - PAT scope restrictions
 95 | 
 96 | 2. MCP Protocol notes
 97 |    - Tools for operations
 98 |    - Resources for data
 99 |    - Strict response format
100 | 
101 | 3. Performance considerations
102 |    - Cache API responses
103 |    - Monitor rate limits
104 |    - Async where beneficial
105 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
 1 | # GitHub MCP Server
 2 | 
 3 | A Model Context Protocol (MCP) server implementation for interacting with GitHub issues through Cline.
 4 | 
 5 | ## Features
 6 | 
 7 | - List GitHub issues from a repository
 8 | - Create new GitHub issues
 9 | - Error handling and validation
10 | - Secure authentication via environment variables
11 | 
12 | ## Installation
13 | 
14 | 1. Clone the repository:
15 | ```bash
16 | git clone https://github.com/timbuchinger/mcp-github.git
17 | cd mcp-github
18 | ```
19 | 
20 | 2. Install dependencies with uv:
21 | ```bash
22 | pip install uv
23 | uv venv
24 | source .venv/bin/activate  # On Windows: .venv\Scripts\activate
25 | uv pip install -r requirements.txt
26 | ```
27 | 
28 | 3. Copy the environment template and configure your GitHub token:
29 | ```bash
30 | cp .env.template .env
31 | ```
32 | 
33 | Edit `.env` and add your GitHub Personal Access Token:
34 | ```bash
35 | GITHUB_TOKEN=your_token_here
36 | ```
37 | 
38 | To create a GitHub Personal Access Token:
39 | 1. Go to GitHub Settings -> Developer settings -> Personal access tokens
40 | 2. Generate a new token with `repo` scope
41 | 3. Copy the token and paste it in your `.env` file
42 | 
43 | ## Usage
44 | 
45 | Run the MCP server:
46 | ```bash
47 | python -m src.mcp_github.server
48 | ```
49 | 
50 | The server will start and expose two tools to Cline:
51 | 
52 | ### get_issues
53 | Get a list of issues from a GitHub repository:
54 | ```json
55 | {
56 |   "repo": "owner/repo"
57 | }
58 | ```
59 | 
60 | ### create_issue
61 | Create a new issue in a GitHub repository:
62 | ```json
63 | {
64 |   "repo": "owner/repo",
65 |   "title": "Issue title",
66 |   "body": "Issue description"
67 | }
68 | ```
69 | 
70 | ## Error Handling
71 | 
72 | The server handles common errors:
73 | - Missing GitHub token
74 | - Invalid repository name
75 | - Missing required parameters
76 | - GitHub API errors
77 | 
78 | Error responses include descriptive messages to help troubleshoot issues.
79 | 
80 | ## Development
81 | 
82 | The project uses uv for dependency management. To set up a development environment:
83 | 
84 | ```bash
85 | # Install all dependencies (including dev dependencies)
86 | uv pip install -r requirements.txt
87 | 
88 | # Run tests
89 | pytest
90 | 
91 | # Format code
92 | black .
93 | 
94 | # Type checking
95 | mypy .
96 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/__init__.py:
--------------------------------------------------------------------------------

```python
1 | """GitHub MCP server for Cline."""
2 | 
3 | __version__ = "0.1.0"
4 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
1 | PyGithub>=2.6.0
2 | mcp>=1.2.1
3 | pytest>=7.4.0
4 | black>=23.7.0
5 | mypy>=1.5.1
6 | 
```

--------------------------------------------------------------------------------
/cline_mcp_settings.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "mcpServers": {
 3 |     "github": {
 4 |       "command": "uv",
 5 |       "args": [
 6 |         "--directory",
 7 |         "/fully/qualified/path/to/mcp-github/",
 8 |         "run",
 9 |         "python",
10 |         "-m",
11 |         "mcp_github.server"
12 |       ],
13 |       "env": {
14 |         "GITHUB_TOKEN": "YOUR_GITHUB_TOKEN",
15 |         "LOG_LEVEL": "INFO"
16 |       },
17 |       "disabled": true,
18 |       "autoApprove": []
19 |     }
20 |   }
21 | }
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp_github"
 3 | version = "0.1.0"
 4 | description = "GitHub MCP server for Cline"
 5 | authors = [{name = "Tim"}]
 6 | readme = "README.md"
 7 | requires-python = ">=3.10"
 8 | dependencies = [
 9 |     "PyGithub>=2.6.0",
10 |     "mcp>=1.2.1"
11 | ]
12 | 
13 | [project.optional-dependencies]
14 | dev = [
15 |     "pytest>=7.4.0",
16 |     "pytest-asyncio>=0.23.0",
17 |     "black>=23.7.0",
18 |     "mypy>=1.5.1"
19 | ]
20 | 
21 | [build-system]
22 | requires = ["hatchling"]
23 | build-backend = "hatchling.build"
24 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/commands/__init__.py:
--------------------------------------------------------------------------------

```python
 1 | """Command implementations for GitHub operations."""
 2 | 
 3 | from abc import ABC, abstractmethod
 4 | from typing import Any, Dict
 5 | 
 6 | 
 7 | class IssueCommand(ABC):
 8 |     """Base class for GitHub issue-related commands."""
 9 | 
10 |     @abstractmethod
11 |     def execute(self) -> Dict[str, Any]:
12 |         """Execute the command.
13 | 
14 |         Returns:
15 |             Dict containing the command result
16 | 
17 |         Raises:
18 |             GitHubError: If there is an error executing the command
19 |         """
20 |         pass
21 | 
```

--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------

```markdown
 1 | ---
 2 | name: Bug report
 3 | about: Create a report to help us improve
 4 | title: ''
 5 | labels: ''
 6 | assignees: ''
 7 | 
 8 | ---
 9 | 
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 | 
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 | 
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 | 
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 | 
26 | **Desktop (please complete the following information):**
27 |  - Python [e.g. 3.11]
28 |  - Version [e.g. v0.1.0]
29 | 
30 | **Additional context**
31 | Add any other context about the problem here.
32 | 
```

--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------

```yaml
 1 | name: CI
 2 | 
 3 | on:
 4 |   push:
 5 |     branches: [ "main" ]
 6 |   pull_request:
 7 |     branches: [ "main" ]
 8 | 
 9 | jobs:
10 |   test:
11 |     runs-on: ubuntu-latest
12 |     strategy:
13 |       matrix:
14 |         python-version: ["3.10", "3.11", "3.12"]
15 | 
16 |     steps:
17 |       - uses: actions/checkout@v4
18 | 
19 |       - name: Set up Python
20 |         uses: actions/setup-python@v5
21 |         with:
22 |           python-version: ${{ matrix.python-version }}
23 | 
24 |       - name: Install uv
25 |         run: curl -LsSf https://astral.sh/uv/install.sh | sh
26 | 
27 |       - name: Install dependencies
28 |         run: |
29 |           uv pip install --system -e ".[dev]"
30 | 
31 |       - name: Check formatting with Black
32 |         run: black --check src tests
33 | 
34 |       - name: Type checking with MyPy
35 |         run: mypy src tests
36 | 
37 |       - name: Run tests with pytest
38 |         run: pytest tests -v
39 |         env:
40 |           GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
41 | 
```

--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------

```yaml
 1 | name: Release
 2 | 
 3 | on:
 4 |   workflow_dispatch:
 5 |     inputs:
 6 |       version:
 7 |         description: 'Version number (e.g., v1.0.0)'
 8 |         required: true
 9 |         type: string
10 | 
11 | jobs:
12 |   release:
13 |     permissions:
14 |       contents: write
15 |     runs-on: ubuntu-latest
16 |     steps:
17 |     - uses: actions/checkout@v3
18 |       with:
19 |         fetch-depth: 0
20 | 
21 |     - name: Validate version format
22 |       run: |
23 |         if ! [[ ${{ github.event.inputs.version }} =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
24 |           echo "Error: Version must be in format v1.0.0"
25 |           exit 1
26 |         fi
27 | 
28 |     - name: Create Release Tag
29 |       run: |
30 |         git tag ${{ github.event.inputs.version }}
31 |         git push origin ${{ github.event.inputs.version }}
32 | 
33 |     - name: Create GitHub Release
34 |       uses: softprops/action-gh-release@v1
35 |       with:
36 |         tag_name: ${{ github.event.inputs.version }}
37 |         name: Release ${{ github.event.inputs.version }}
38 |         draft: true
39 |         prerelease: false
40 |         generate_release_notes: true
41 |       env:
42 |         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
43 | 
```

--------------------------------------------------------------------------------
/memory-bank/projectbrief.md:
--------------------------------------------------------------------------------

```markdown
 1 | # GitHub MCP Server Project Brief
 2 | 
 3 | ## Overview
 4 | 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.
 5 | 
 6 | ## Core Requirements
 7 | - Integration with GitHub's REST API
 8 | - Read and list GitHub issues
 9 | - Create new GitHub issues
10 | - Seamless integration with Cline
11 | - Clear error handling and feedback
12 | - Secure credential management
13 | 
14 | ## Goals
15 | 1. Enable efficient GitHub issue management through Cline
16 | 2. Provide a robust and reliable MCP server implementation
17 | 3. Ensure secure handling of GitHub credentials
18 | 4. Maintain clean separation of concerns between MCP protocol and GitHub API interaction
19 | 
20 | ## Success Criteria
21 | - Successfully read existing GitHub issues
22 | - Create new issues with proper formatting
23 | - Handle authentication securely
24 | - Provide clear feedback through Cline's interface
25 | - Maintain stable connection between Cline and GitHub
26 | 
27 | ## Scope
28 | ### In Scope
29 | - GitHub issue reading
30 | - GitHub issue creation
31 | - Basic authentication
32 | - Error handling
33 | - MCP protocol implementation
34 | 
35 | ### Out of Scope
36 | - Issue comments management
37 | - Pull request interactions
38 | - Repository management
39 | - GitHub project board integration
40 | 
41 | ## Technical Constraints
42 | - Python-based implementation
43 | - MCP protocol compliance
44 | - GitHub API rate limiting consideration
45 | - OAuth authentication requirements
46 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/commands/create.py:
--------------------------------------------------------------------------------

```python
 1 | """Command for creating GitHub issues."""
 2 | 
 3 | from typing import Any, Dict
 4 | 
 5 | from github.Issue import Issue
 6 | 
 7 | from ..github import GitHubClient
 8 | from . import IssueCommand
 9 | 
10 | 
11 | class CreateIssueCommand(IssueCommand):
12 |     """Command for creating a new issue in a GitHub repository."""
13 | 
14 |     def __init__(self, github_client: GitHubClient, repo: str, title: str, body: str):
15 |         """Initialize command.
16 | 
17 |         Args:
18 |             github_client: Authenticated GitHub client
19 |             repo: Repository name in format "owner/repo"
20 |             title: Issue title
21 |             body: Issue body/description
22 |         """
23 |         self.github_client = github_client
24 |         self.repo = repo
25 |         self.title = title
26 |         self.body = body
27 | 
28 |     def _format_issue(self, issue: Issue) -> Dict[str, Any]:
29 |         """Format a GitHub issue into a dictionary.
30 | 
31 |         Args:
32 |             issue: GitHub issue to format
33 | 
34 |         Returns:
35 |             Dictionary containing formatted issue data
36 |         """
37 |         return {
38 |             "number": issue.number,
39 |             "title": issue.title,
40 |             "body": issue.body,
41 |             "state": issue.state,
42 |             "created_at": issue.created_at.isoformat(),
43 |             "updated_at": issue.updated_at.isoformat(),
44 |         }
45 | 
46 |     def execute(self) -> Dict[str, Any]:
47 |         """Execute the create issue command.
48 | 
49 |         Returns:
50 |             Dictionary containing created issue data
51 | 
52 |         Raises:
53 |             GitHubError: If there is an error creating the issue
54 |         """
55 |         issue = self.github_client.create_issue(self.repo, self.title, self.body)
56 |         return {"issue": self._format_issue(issue)}
57 | 
```

--------------------------------------------------------------------------------
/memory-bank/progress.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Progress Tracking
 2 | 
 3 | ## Project Status: Initialization Phase
 4 | 
 5 | ## Completed
 6 | ✅ Project Planning
 7 | - Memory bank structure created
 8 | - Core documentation established
 9 | - Architecture defined
10 | - Technical requirements documented
11 | 
12 | ## In Progress
13 | 🔄 Project Setup
14 | - Repository initialization
15 | - Development environment configuration
16 | - Initial package structure
17 | 
18 | ## Upcoming
19 | ⏳ Foundation Implementation
20 | - MCP server skeleton
21 | - GitHub API client
22 | - Authentication handling
23 | - Basic tool implementation
24 | 
25 | ## Development Roadmap
26 | 
27 | ### Phase 1: Setup & Foundation
28 | - [ ] Initialize Python project
29 | - [ ] Configure development environment
30 | - [ ] Set up testing framework
31 | - [ ] Implement basic MCP server
32 | - [ ] Create GitHub API client
33 | 
34 | ### Phase 2: Core Features
35 | - [ ] Implement authentication
36 | - [ ] Add rate limiting
37 | - [ ] Create issue reading tool
38 | - [ ] Create issue creation tool
39 | - [ ] Add error handling
40 | 
41 | ### Phase 3: Enhancement
42 | - [ ] Implement caching
43 | - [ ] Add comprehensive testing
44 | - [ ] Improve error messages
45 | - [ ] Optimize performance
46 | - [ ] Add documentation
47 | 
48 | ## Known Issues
49 | *(No issues yet - project in initialization phase)*
50 | 
51 | ## Testing Status
52 | - Unit Tests: Not started
53 | - Integration Tests: Not started
54 | - End-to-End Tests: Not started
55 | - Coverage: 0%
56 | 
57 | ## Documentation Status
58 | ✅ Memory Bank
59 | - Project brief
60 | - Product context
61 | - System patterns
62 | - Technical context
63 | - Active context
64 | - Progress tracking
65 | 
66 | ⏳ Code Documentation
67 | - [ ] README
68 | - [ ] API documentation
69 | - [ ] Setup guide
70 | - [ ] Usage examples
71 | 
72 | ## Metrics
73 | *(To be tracked once implementation begins)*
74 | - Code coverage
75 | - API response times
76 | - Error rates
77 | - Rate limit usage
78 | 
79 | ## Notes
80 | Initial setup phase in progress. Focus on establishing solid foundation before moving to implementation.
81 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/commands/get.py:
--------------------------------------------------------------------------------

```python
 1 | """Command for retrieving GitHub issues."""
 2 | 
 3 | from typing import Any, Dict, List, Optional
 4 | 
 5 | from github.Issue import Issue
 6 | 
 7 | from ..github import GitHubClient
 8 | from . import IssueCommand
 9 | 
10 | 
11 | class GetIssuesCommand(IssueCommand):
12 |     """Command for retrieving issues from a GitHub repository."""
13 | 
14 |     def __init__(
15 |         self,
16 |         github_client: GitHubClient,
17 |         repo: str,
18 |         state: str = "open",
19 |         query: Optional[str] = None,
20 |     ):
21 |         """Initialize command.
22 | 
23 |         Args:
24 |             github_client: Authenticated GitHub client
25 |             repo: Repository name in format "owner/repo"
26 |             state: State of issues to retrieve ("open", "closed", or "all")
27 |             query: Optional search query to filter issues
28 |         """
29 |         self.github_client = github_client
30 |         self.repo = repo
31 |         self.state = state
32 |         self.query = query
33 | 
34 |     def _format_issue(self, issue: Issue) -> Dict[str, Any]:
35 |         """Format a GitHub issue into a dictionary.
36 | 
37 |         Args:
38 |             issue: GitHub issue to format
39 | 
40 |         Returns:
41 |             Dictionary containing formatted issue data
42 |         """
43 |         return {
44 |             "number": issue.number,
45 |             "title": issue.title,
46 |             "body": issue.body,
47 |             "state": issue.state,
48 |             "created_at": issue.created_at.isoformat(),
49 |             "updated_at": issue.updated_at.isoformat(),
50 |         }
51 | 
52 |     def execute(self) -> Dict[str, Any]:
53 |         """Execute the get issues command.
54 | 
55 |         Returns:
56 |             Dictionary containing list of issues
57 | 
58 |         Raises:
59 |             GitHubError: If there is an error retrieving the issues
60 |         """
61 |         issues = self.github_client.get_issues(self.repo, self.state, self.query)
62 |         return {"issues": [self._format_issue(issue) for issue in issues]}
63 | 
```

--------------------------------------------------------------------------------
/memory-bank/productContext.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Product Context
 2 | 
 3 | ## Purpose
 4 | 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.
 5 | 
 6 | ## Problems Solved
 7 | 1. **Context Switching**
 8 |    - Eliminates need to switch between Cline and GitHub web interface
 9 |    - Maintains flow of development conversations
10 |    
11 | 2. **Issue Management Efficiency**
12 |    - Direct issue creation from development discussions
13 |    - Quick access to existing issues for reference
14 |    
15 | 3. **Integration Cohesion**
16 |    - Seamless connection between Cline and GitHub
17 |    - Consistent experience within Cline's interface
18 | 
19 | ## Intended Workflow
20 | 1. User discusses development tasks with Cline
21 | 2. Cline can immediately check existing issues or create new ones
22 | 3. Issue creation/reading happens without leaving the conversation
23 | 4. Smooth transition between discussion and issue management
24 | 
25 | ## User Experience Goals
26 | - **Simplicity**: Straightforward issue management within Cline
27 | - **Speed**: Quick access to GitHub functionality
28 | - **Reliability**: Consistent and dependable operation
29 | - **Security**: Safe handling of GitHub credentials
30 | - **Clarity**: Clear feedback about operations and status
31 | 
32 | ## Integration Points
33 | 1. **Cline Interface**
34 |    - Natural language processing of issue-related requests
35 |    - Structured response formatting
36 |    
37 | 2. **GitHub API**
38 |    - Authentication and authorization
39 |    - Issue creation and retrieval
40 |    
41 | 3. **MCP Protocol**
42 |    - Standardized communication format
43 |    - Tool and resource exposure
44 | 
45 | ## Success Indicators
46 | - Reduced context switching for developers
47 | - Increased efficiency in issue management
48 | - Positive user feedback on integration
49 | - Reliable and consistent operation
50 | 
```

--------------------------------------------------------------------------------
/memory-bank/activeContext.md:
--------------------------------------------------------------------------------

```markdown
 1 | # Active Context
 2 | 
 3 | ## Current Phase
 4 | Initial project setup and foundation building
 5 | 
 6 | ## Recent Changes
 7 | - Created memory bank structure
 8 | - Defined project requirements and scope
 9 | - Established technical architecture
10 | - Documented development setup
11 | 
12 | ## Current Focus
13 | 1. Project Infrastructure
14 |    - Repository setup
15 |    - Development environment configuration
16 |    - Initial package structure
17 | 
18 | 2. Core Implementation
19 |    - MCP server skeleton
20 |    - GitHub API client foundation
21 |    - Basic authentication flow
22 | 
23 | 3. Tool Design
24 |    - Issue reading implementation
25 |    - Issue creation implementation
26 |    - Error handling framework
27 | 
28 | ## Active Decisions
29 | 
30 | ### Authentication Strategy
31 | - Using Personal Access Token (PAT)
32 | - Secure storage via environment variables
33 | - Token scope limited to issue management
34 | 
35 | ### API Integration
36 | - PyGithub as primary GitHub API client
37 | - Async operations where beneficial
38 | - Caching for rate limit management
39 | 
40 | ### Tool Structure
41 | - Separate tools for read and create operations
42 | - Consistent response formatting
43 | - Clear error messages
44 | 
45 | ## Immediate Priorities
46 | 1. Set up basic project structure
47 |    - Initialize Python package
48 |    - Configure development tools
49 |    - Set up testing framework
50 | 
51 | 2. Implement MCP server foundation
52 |    - Basic server setup
53 |    - Tool registration
54 |    - Error handling
55 | 
56 | 3. Create GitHub API client
57 |    - Authentication implementation
58 |    - Basic API operations
59 |    - Rate limit handling
60 | 
61 | ## Pending Considerations
62 | - Cache implementation details
63 | - Rate limit strategies
64 | - Error reporting format
65 | - Testing approach
66 | 
67 | ## Next Steps
68 | 1. Create initial project structure
69 | 2. Set up development environment
70 | 3. Implement basic MCP server
71 | 4. Add GitHub authentication
72 | 5. Create first tool implementation
73 | 
74 | ## Open Questions
75 | - Cache duration for API responses
76 | - Error message format preferences
77 | - Rate limit handling strategy
78 | - Testing coverage requirements
79 | 
```

--------------------------------------------------------------------------------
/memory-bank/systemPatterns.md:
--------------------------------------------------------------------------------

```markdown
  1 | # System Patterns
  2 | 
  3 | ## Architecture Overview
  4 | ```mermaid
  5 | graph TD
  6 |     C[Cline] --- M[MCP Protocol Layer]
  7 |     M --- S[Server Core]
  8 |     S --- GH[GitHub API Client]
  9 |     GH --- API[GitHub REST API]
 10 | ```
 11 | 
 12 | ## Core Components
 13 | 
 14 | ### 1. MCP Protocol Layer
 15 | - Handles communication with Cline
 16 | - Implements MCP server protocol
 17 | - Exposes tools and resources
 18 | - Manages request/response lifecycle
 19 | 
 20 | ### 2. Server Core
 21 | - Central coordination layer
 22 | - Authentication management
 23 | - Error handling and logging
 24 | - Request validation
 25 | 
 26 | ### 3. GitHub API Client
 27 | - GitHub REST API interaction
 28 | - Rate limiting management
 29 | - Response parsing
 30 | - Error translation
 31 | 
 32 | ## Design Patterns
 33 | 
 34 | ### 1. Command Pattern
 35 | - Each GitHub operation encapsulated as a command
 36 | - Standardized execution flow
 37 | - Consistent error handling
 38 | ```python
 39 | class IssueCommand:
 40 |     def execute(self):
 41 |         pass
 42 | ```
 43 | 
 44 | ### 2. Factory Pattern
 45 | - Tool and resource creation
 46 | - Command instantiation
 47 | - Response formatting
 48 | ```python
 49 | class ToolFactory:
 50 |     def create_tool(self, name: str):
 51 |         pass
 52 | ```
 53 | 
 54 | ### 3. Strategy Pattern
 55 | - Authentication strategies
 56 | - Response formatting
 57 | - Rate limiting approaches
 58 | ```python
 59 | class AuthStrategy:
 60 |     def authenticate(self):
 61 |         pass
 62 | ```
 63 | 
 64 | ## Component Relationships
 65 | 
 66 | ### Tool Implementation
 67 | ```mermaid
 68 | graph LR
 69 |     T[Tool Request] --> V[Validator]
 70 |     V --> C[Command]
 71 |     C --> G[GitHub API]
 72 |     G --> R[Response]
 73 |     R --> F[Formatter]
 74 |     F --> TR[Tool Response]
 75 | ```
 76 | 
 77 | ### Resource Implementation
 78 | ```mermaid
 79 | graph LR
 80 |     R[Resource Request] --> C[Cache Check]
 81 |     C --> |Miss| G[GitHub API]
 82 |     C --> |Hit| RC[Return Cached]
 83 |     G --> S[Store Cache]
 84 |     S --> RR[Return Response]
 85 | ```
 86 | 
 87 | ## Error Handling
 88 | - Hierarchical error types
 89 | - Consistent error translation
 90 | - Clear error messages
 91 | - Rate limit handling
 92 | 
 93 | ## Data Flow
 94 | 1. Request received through MCP
 95 | 2. Request validated
 96 | 3. Command created and executed
 97 | 4. Response formatted
 98 | 5. Response returned through MCP
 99 | 
100 | ## Security Patterns
101 | - Credential encryption
102 | - Token-based authentication
103 | - Secure storage practices
104 | - Request validation
105 | 
106 | ## Testing Patterns
107 | - Unit tests per component
108 | - Integration tests for GitHub API
109 | - Mock MCP client for testing
110 | - Error scenario coverage
111 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/github.py:
--------------------------------------------------------------------------------

```python
 1 | """GitHub API client implementation."""
 2 | 
 3 | from typing import List, Optional
 4 | 
 5 | from github import Github, GithubException
 6 | from github.Issue import Issue
 7 | 
 8 | 
 9 | class GitHubError(Exception):
10 |     """Custom exception for GitHub API errors."""
11 | 
12 |     def __init__(self, message: str, status: Optional[int] = None):
13 |         self.status = status
14 |         super().__init__(message)
15 | 
16 | 
17 | class GitHubClient:
18 |     """Client for interacting with GitHub API."""
19 | 
20 |     def __init__(self, token: str):
21 |         """Initialize GitHub client with authentication token.
22 | 
23 |         Args:
24 |             token: GitHub personal access token
25 |         """
26 |         self.api = Github(token)
27 | 
28 |     def get_issues(
29 |         self, repo: str, state: str = "open", query: Optional[str] = None
30 |     ) -> List[Issue]:
31 |         """Get list of issues from a repository.
32 | 
33 |         Args:
34 |             repo: Repository name in format "owner/repo"
35 |             state: State of issues to retrieve ("open", "closed", or "all")
36 |             query: Optional search query to filter issues
37 | 
38 |         Returns:
39 |             List of GitHub issues
40 | 
41 |         Raises:
42 |             GitHubError: If there is an error accessing the issues
43 |         """
44 |         try:
45 |             repository = self.api.get_repo(repo)
46 |             if query:
47 |                 query_str = f"repo:{repo} {query} state:{state}"
48 |                 return list(self.api.search_issues(query_str))
49 |             return list(repository.get_issues(state=state))
50 |         except GithubException as e:
51 |             raise GitHubError(str(e.data.get("message", "Unknown error")), e.status)
52 | 
53 |     def create_issue(self, repo: str, title: str, body: str) -> Issue:
54 |         """Create a new issue in a repository.
55 | 
56 |         Args:
57 |             repo: Repository name in format "owner/repo"
58 |             title: Issue title
59 |             body: Issue body/description
60 | 
61 |         Returns:
62 |             Created GitHub issue
63 | 
64 |         Raises:
65 |             GitHubError: If there is an error creating the issue
66 |         """
67 |         try:
68 |             repository = self.api.get_repo(repo)
69 |             return repository.create_issue(title=title, body=body)
70 |         except GithubException as e:
71 |             raise GitHubError(str(e.data.get("message", "Unknown error")), e.status)
72 | 
```

--------------------------------------------------------------------------------
/memory-bank/techContext.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Technical Context
  2 | 
  3 | ## Technologies
  4 | 
  5 | ### Core Stack
  6 | - **Python 3.9+**
  7 |   - Modern Python features
  8 |   - Type hinting support
  9 |   - Async capabilities
 10 | 
 11 | ### Dependencies
 12 | 1. **MCP SDK**
 13 |    - `@modelcontextprotocol/sdk`
 14 |    - Server implementation
 15 |    - Protocol handling
 16 | 
 17 | 2. **GitHub API**
 18 |    - PyGithub package
 19 |    - REST API interaction
 20 |    - Rate limit handling
 21 | 
 22 | 3. **Development Tools**
 23 |    - uv (dependency management)
 24 |    - Black (code formatting)
 25 |    - MyPy (static type checking)
 26 |    - Pytest (testing framework)
 27 | 
 28 | ## Development Setup
 29 | 
 30 | ### Environment Requirements
 31 | ```bash
 32 | # Python version
 33 | Python 3.9+
 34 | 
 35 | # Required system packages
 36 | git
 37 | python3-venv
 38 | python3-pip
 39 | 
 40 | # Virtual environment
 41 | python -m venv .venv
 42 | source .venv/bin/activate
 43 | ```
 44 | 
 45 | ### Project Structure
 46 | ```
 47 | mcp-github/
 48 | ├── pyproject.toml        # Project metadata and dependencies
 49 | ├── poetry.lock          # Locked dependencies
 50 | ├── src/
 51 | │   └── mcp_github/     # Main package
 52 | │       ├── __init__.py
 53 | │       ├── server.py    # MCP server implementation
 54 | │       ├── github.py    # GitHub API client
 55 | │       ├── commands/    # Command implementations
 56 | │       └── tools/       # MCP tool definitions
 57 | ├── tests/              # Test suite
 58 | └── docs/              # Documentation
 59 | ```
 60 | 
 61 | ## Technical Constraints
 62 | 
 63 | ### 1. Authentication
 64 | - GitHub Personal Access Token required
 65 | - Secure token storage
 66 | - Token scope limitations
 67 | 
 68 | ### 2. Rate Limiting
 69 | - GitHub API rate limits
 70 | - Cache implementation
 71 | - Rate limit handling
 72 | 
 73 | ### 3. Dependencies
 74 | - Minimum dependency footprint
 75 | - Maintained packages only
 76 | - Security considerations
 77 | 
 78 | ### 4. Performance
 79 | - Async operations where beneficial
 80 | - Response caching
 81 | - Efficient API usage
 82 | 
 83 | ## Configuration
 84 | 
 85 | ### Environment Variables
 86 | ```bash
 87 | GITHUB_TOKEN=            # GitHub Personal Access Token
 88 | GITHUB_API_URL=         # GitHub API URL (optional)
 89 | LOG_LEVEL=             # Logging level (optional)
 90 | CACHE_TTL=            # Cache time-to-live (optional)
 91 | ```
 92 | 
 93 | ### Development Configuration
 94 | ```toml
 95 | [project]
 96 | name = "mcp-github"
 97 | version = "0.1.0"
 98 | description = "GitHub MCP server for Cline"
 99 | requires-python = ">=3.10"
100 | dependencies = [
101 |     "PyGithub>=2.6.0",
102 |     "mcp>=1.2.1"
103 | ]
104 | 
105 | [project.optional-dependencies]
106 | dev = [
107 |     "pytest>=7.4.0",
108 |     "black>=23.7.0",
109 |     "mypy>=1.5.1"
110 | ]
111 | 
112 | [build-system]
113 | requires = ["hatchling"]
114 | build-backend = "hatchling.build"
115 | ```
116 | 
117 | ## Testing Strategy
118 | - Unit tests for each component
119 | - Integration tests with GitHub API
120 | - Mock MCP client
121 | - GitHub API response mocking
122 | - Comprehensive error testing
123 | 
124 | ## Error Handling
125 | - Custom exception hierarchy
126 | - Detailed error messages
127 | - Error translation to MCP format
128 | - Logging for debugging
129 | 
130 | ## Security Considerations
131 | - Token validation
132 | - Secure credential storage
133 | - Input validation
134 | - Rate limit protection
135 | - Error message sanitization
136 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/tools/__init__.py:
--------------------------------------------------------------------------------

```python
  1 | """Tool definitions for GitHub MCP server."""
  2 | 
  3 | import json
  4 | from typing import Any, Dict
  5 | 
  6 | from ..commands.create import CreateIssueCommand
  7 | from ..commands.get import GetIssuesCommand
  8 | from ..github import GitHubClient, GitHubError
  9 | 
 10 | 
 11 | def get_github_client() -> GitHubClient:
 12 |     """Get an authenticated GitHub client from environment variables.
 13 | 
 14 |     Returns:
 15 |         Authenticated GitHub client
 16 | 
 17 |     Raises:
 18 |         GitHubError: If GitHub token is not configured
 19 |     """
 20 |     import os
 21 | 
 22 |     token = os.getenv("GITHUB_TOKEN")
 23 |     if not token:
 24 |         raise GitHubError("GITHUB_TOKEN environment variable not set")
 25 | 
 26 |     return GitHubClient(token)
 27 | 
 28 | 
 29 | def format_error(error: GitHubError) -> Dict[str, Any]:
 30 |     """Format a GitHub error for MCP response.
 31 | 
 32 |     Args:
 33 |         error: GitHub error to format
 34 | 
 35 |     Returns:
 36 |         Dictionary containing error details
 37 |     """
 38 |     return {
 39 |         "content": [
 40 |             {
 41 |                 "type": "text",
 42 |                 "text": str(error),
 43 |             }
 44 |         ],
 45 |         "isError": True,
 46 |     }
 47 | 
 48 | 
 49 | async def get_issues(arguments: Dict[str, Any]) -> Dict[str, Any]:
 50 |     """MCP tool implementation for getting GitHub issues.
 51 | 
 52 |     Args:
 53 |         arguments: Tool arguments containing repository
 54 | 
 55 |     Returns:
 56 |         Tool response containing issues or error
 57 |     """
 58 |     try:
 59 |         repo = arguments.get("repo")
 60 |         if not repo:
 61 |             raise GitHubError("Repository not specified")
 62 | 
 63 |         client = get_github_client()
 64 |         command = GetIssuesCommand(client, repo)
 65 |         result = command.execute()
 66 | 
 67 |         return {
 68 |             "content": [
 69 |                 {
 70 |                     "type": "text",
 71 |                     "text": json.dumps(result, indent=2),
 72 |                 }
 73 |             ],
 74 |         }
 75 |     except GitHubError as e:
 76 |         return format_error(e)
 77 | 
 78 | 
 79 | async def create_issue(arguments: Dict[str, Any]) -> Dict[str, Any]:
 80 |     """MCP tool implementation for creating GitHub issues.
 81 | 
 82 |     Args:
 83 |         arguments: Tool arguments containing repository, title, and body
 84 | 
 85 |     Returns:
 86 |         Tool response containing created issue or error
 87 |     """
 88 |     try:
 89 |         repo = arguments.get("repo")
 90 |         title = arguments.get("title")
 91 |         body = arguments.get("body", "")
 92 | 
 93 |         if not repo:
 94 |             raise GitHubError("Repository not specified")
 95 |         if not title:
 96 |             raise GitHubError("Issue title not specified")
 97 | 
 98 |         client = get_github_client()
 99 |         command = CreateIssueCommand(client, repo, title, body)
100 |         result = command.execute()
101 | 
102 |         return {
103 |             "content": [
104 |                 {
105 |                     "type": "text",
106 |                     "text": json.dumps(result, indent=2),
107 |                 }
108 |             ],
109 |         }
110 |     except GitHubError as e:
111 |         return format_error(e)
112 | 
```

--------------------------------------------------------------------------------
/src/mcp_github/server.py:
--------------------------------------------------------------------------------

```python
  1 | """MCP server implementation for GitHub integration."""
  2 | 
  3 | import logging
  4 | import sys
  5 | from typing import Any, Awaitable, Callable, Dict
  6 | 
  7 | import mcp.types as types
  8 | from mcp.server import Server
  9 | from mcp.shared.exceptions import McpError
 10 | from mcp.types import (
 11 |     INTERNAL_ERROR,
 12 |     CallToolRequestParams,
 13 |     ErrorData,
 14 |     ListResourcesRequest,
 15 |     ListResourceTemplatesRequest,
 16 |     ListToolsRequest,
 17 |     TextContent,
 18 |     Tool,
 19 | )
 20 | 
 21 | from .tools import create_issue, get_issues
 22 | 
 23 | # Initialize server at module level
 24 | server = Server("github")
 25 | logging.basicConfig(stream=sys.stderr, level=logging.INFO)
 26 | logger = logging.getLogger("mcp_github")
 27 | 
 28 | 
 29 | # Set up request handlers
 30 | @server.list_resources()
 31 | async def list_resources():
 32 |     """List available resources (none for this server)."""
 33 |     return []
 34 | 
 35 | 
 36 | @server.list_resource_templates()
 37 | async def list_resource_templates():
 38 |     """List available resource templates (none for this server)."""
 39 |     return []
 40 | 
 41 | 
 42 | @server.list_tools()
 43 | async def list_tools() -> list[Tool]:
 44 |     return [
 45 |         Tool(
 46 |             name="get_issues",
 47 |             description="Get list of issues from a GitHub repository",
 48 |             inputSchema={
 49 |                 "type": "object",
 50 |                 "properties": {
 51 |                     "repo": {
 52 |                         "type": "string",
 53 |                         "description": "Repository name in format owner/repo",
 54 |                     },
 55 |                 },
 56 |                 "required": ["repo"],
 57 |             },
 58 |         ),
 59 |         Tool(
 60 |             name="create_issue",
 61 |             description="Create a new issue in a GitHub repository",
 62 |             inputSchema={
 63 |                 "type": "object",
 64 |                 "properties": {
 65 |                     "repo": {
 66 |                         "type": "string",
 67 |                         "description": "Repository name in format owner/repo",
 68 |                     },
 69 |                     "title": {
 70 |                         "type": "string",
 71 |                         "description": "Issue title",
 72 |                     },
 73 |                     "body": {
 74 |                         "type": "string",
 75 |                         "description": "Issue body/description",
 76 |                     },
 77 |                 },
 78 |                 "required": ["repo", "title"],
 79 |             },
 80 |         ),
 81 |     ]
 82 | 
 83 | 
 84 | @server.call_tool()
 85 | async def handle_call_tool(name: str, arguments: dict | None) -> list[TextContent]:
 86 |     """Handle tool operations."""
 87 |     logger.info(f"Handling tool {name} with arguments {arguments}")
 88 |     if not arguments:
 89 |         arguments = {}
 90 | 
 91 |     try:
 92 |         tool_handlers: Dict[str, Callable[[dict], Awaitable[Any]]] = {
 93 |             "get_issues": get_issues,
 94 |             "create_issue": create_issue,
 95 |         }
 96 | 
 97 |         handler = tool_handlers.get(name)
 98 |         if not handler:
 99 |             raise ValueError(f"Unknown tool: {name}")
100 | 
101 |         result = await handler(arguments)
102 |         return [TextContent(type="text", text=str(result))]
103 | 
104 |     except Exception as e:
105 |         logger.error(f"Error handling tool {name}: {e}")
106 |         logger.exception(e)
107 |         raise McpError(
108 |             ErrorData(
109 |                 code=INTERNAL_ERROR,
110 |                 message=f"Error handling tool {name}: {e}",
111 |             )
112 |         )
113 | 
114 | 
115 | async def run_server():
116 |     """Run the MCP server on stdio transport."""
117 |     from mcp.server.stdio import stdio_server
118 | 
119 |     async with stdio_server() as streams:
120 |         await server.run(streams[0], streams[1], server.create_initialization_options())
121 | 
122 | 
123 | def main():
124 |     """Run the GitHub MCP server."""
125 |     import asyncio
126 | 
127 |     logger.info("Starting GitHub MCP server")
128 |     asyncio.run(run_server())
129 | 
130 | 
131 | if __name__ == "__main__":
132 |     main()
133 | 
```

--------------------------------------------------------------------------------
/tests/test_server.py:
--------------------------------------------------------------------------------

```python
  1 | """Tests for the MCP GitHub server implementation."""
  2 | 
  3 | import asyncio
  4 | from datetime import datetime
  5 | from io import StringIO
  6 | from unittest.mock import AsyncMock, Mock, PropertyMock, patch
  7 | 
  8 | import pytest
  9 | from mcp.shared.exceptions import McpError
 10 | from mcp.types import TextContent
 11 | 
 12 | from mcp_github.github import GitHubError
 13 | from mcp_github.server import (
 14 |     handle_call_tool,
 15 |     list_resource_templates,
 16 |     list_resources,
 17 |     list_tools,
 18 |     server,
 19 | )
 20 | 
 21 | 
 22 | @pytest.fixture
 23 | def mock_transport():
 24 |     """Create mock input/output streams for testing."""
 25 |     input_stream = StringIO()
 26 |     output_stream = StringIO()
 27 |     return input_stream, output_stream
 28 | 
 29 | 
 30 | @pytest.mark.asyncio
 31 | async def test_list_tools():
 32 |     """Test that list_tools returns the expected tool definitions."""
 33 |     tools = await list_tools()
 34 | 
 35 |     assert isinstance(tools, list)
 36 |     assert len(tools) == 2  # get_issues and create_issue
 37 | 
 38 |     # Verify get_issues tool
 39 |     get_issues = next(t for t in tools if t.name == "get_issues")
 40 |     assert get_issues.description == "Get list of issues from a GitHub repository"
 41 |     assert get_issues.inputSchema["required"] == ["repo"]
 42 | 
 43 |     # Verify create_issue tool
 44 |     create_issue = next(t for t in tools if t.name == "create_issue")
 45 |     assert create_issue.description == "Create a new issue in a GitHub repository"
 46 |     assert set(create_issue.inputSchema["required"]) == {"repo", "title"}
 47 | 
 48 | 
 49 | @pytest.mark.asyncio
 50 | async def test_list_resources():
 51 |     """Test that list_resources returns an empty list."""
 52 |     resources = await list_resources()
 53 |     assert isinstance(resources, list)
 54 |     assert len(resources) == 0
 55 | 
 56 | 
 57 | @pytest.mark.asyncio
 58 | async def test_list_resource_templates():
 59 |     """Test that list_resource_templates returns an empty list."""
 60 |     templates = await list_resource_templates()
 61 |     assert isinstance(templates, list)
 62 |     assert len(templates) == 0
 63 | 
 64 | 
 65 | @pytest.mark.asyncio
 66 | @patch("mcp_github.tools.get_github_client")
 67 | async def test_get_issues_tool(mock_get_github_client):
 68 |     """Test the get_issues tool with valid arguments."""
 69 |     # Create a mock GitHub client
 70 |     mock_client = Mock()
 71 |     # Create a mock Issue object
 72 |     mock_issue = Mock()
 73 |     type(mock_issue).number = PropertyMock(return_value=1)
 74 |     type(mock_issue).title = PropertyMock(return_value="Test Issue")
 75 |     type(mock_issue).body = PropertyMock(return_value="Test body")
 76 |     type(mock_issue).state = PropertyMock(return_value="open")
 77 |     type(mock_issue).created_at = PropertyMock(return_value=datetime(2025, 2, 18))
 78 |     type(mock_issue).updated_at = PropertyMock(return_value=datetime(2025, 2, 18))
 79 | 
 80 |     mock_client.get_issues.return_value = [mock_issue]
 81 |     mock_get_github_client.return_value = mock_client
 82 | 
 83 |     response = await handle_call_tool("get_issues", {"repo": "owner/repo"})
 84 | 
 85 |     assert isinstance(response, list)
 86 |     assert len(response) == 1
 87 |     assert response[0].type == "text"
 88 |     assert "Test Issue" in response[0].text
 89 |     mock_client.get_issues.assert_called_once_with("owner/repo", "open", None)
 90 | 
 91 | 
 92 | @pytest.mark.asyncio
 93 | @patch("mcp_github.tools.get_github_client")
 94 | async def test_create_issue_tool(mock_get_github_client):
 95 |     """Test the create_issue tool with valid arguments."""
 96 |     # Create a mock GitHub client
 97 |     mock_client = Mock()
 98 |     # Create a mock Issue object
 99 |     mock_issue = Mock()
100 |     type(mock_issue).number = PropertyMock(return_value=1)
101 |     type(mock_issue).title = PropertyMock(return_value="Test Issue")
102 |     type(mock_issue).body = PropertyMock(return_value="Test Description")
103 |     type(mock_issue).state = PropertyMock(return_value="open")
104 |     type(mock_issue).created_at = PropertyMock(return_value=datetime(2025, 2, 18))
105 |     type(mock_issue).updated_at = PropertyMock(return_value=datetime(2025, 2, 18))
106 | 
107 |     mock_client.create_issue.return_value = mock_issue
108 |     mock_get_github_client.return_value = mock_client
109 | 
110 |     response = await handle_call_tool(
111 |         "create_issue",
112 |         {
113 |             "repo": "owner/repo",
114 |             "title": "Test Issue",
115 |             "body": "Test Description",
116 |         },
117 |     )
118 | 
119 |     assert isinstance(response, list)
120 |     assert len(response) == 1
121 |     assert response[0].type == "text"
122 |     mock_client.create_issue.assert_called_once_with(
123 |         "owner/repo", "Test Issue", "Test Description"
124 |     )
125 | 
126 | 
127 | @pytest.mark.asyncio
128 | async def test_invalid_tool_name():
129 |     """Test that calling an invalid tool name raises an error."""
130 |     with pytest.raises(McpError) as exc_info:
131 |         await handle_call_tool("invalid_tool", {})
132 |     assert "Unknown tool: invalid_tool" in str(exc_info.value)
133 | 
134 | 
135 | @pytest.mark.asyncio
136 | @patch("mcp_github.tools.get_github_client")
137 | async def test_missing_required_arguments(mock_get_github_client):
138 |     """Test that calling a tool with missing required arguments raises an error."""
139 |     response = await handle_call_tool(
140 |         "create_issue", {"body": "Missing required repo and title"}
141 |     )
142 |     assert len(response) == 1
143 |     assert response[0].type == "text"
144 |     assert "Repository not specified" in response[0].text
145 | 
146 | 
147 | @pytest.mark.asyncio
148 | @patch("mcp_github.tools.get_github_client")
149 | async def test_null_arguments(mock_get_github_client):
150 |     """Test that calling a tool with null arguments doesn't crash."""
151 |     response = await handle_call_tool("get_issues", None)
152 |     assert len(response) == 1
153 |     assert response[0].type == "text"
154 |     assert "Repository not specified" in response[0].text
155 | 
```