PTENES
MODULE 4.4

🌐 Integrating with a REST API

Create an MCP Server that uses the GitHub API—list repos, search issues, and create issues via Claude.

6
Topics
45
Minutes
Intermediate
Level
Practical
Type
1

🌐 Understanding the GitHub API

The GitHub API is one of the best-documented and most widely used REST APIs in the world. It lets you programmatically access practically everything you do in the web interface—repositories, issues, pull requests, commits, organizations. For our MCP Server, we’ll use this API as the foundation for building powerful tools Claude can use to interact with your GitHub projects.

💎 Core Concept

The GitHub REST API is available at api.github.com. The main endpoints we'll use are: GET /user/repos to list your repositories, GET /repos/{owner}/{repo}/issues to search for issues in a specific repository, and POST /repos/{owner}/{repo}/issues to create new issues. All responses are in JSON format, making it easier to parse and format the data. The API has rate limits — 5,000 requests per hour with authentication — which your server must respect.

📊 Data and Research

The GitHub API processes more than billions of requests per day and is used by virtually all modern development tools—CI/CD, IDEs, bots, dashboards. With token authentication, the rate limit increases to 5,000 requests/hour, more than enough for a personal MCP Server. Without authentication, the limit drops to just 60 requests/hour, making any practical use unfeasible.

💡 Practical Tip

Before writing any code, test the API manually with curl or with tools like Insomnia/Postman. Run curl https://api.github.com/users/{seu-usuario}/repos to see the structure of the returned JSON. This makes implementing the MCP Server tools much easier, since you’ll know exactly which fields to extract and format.

2

🔑 Token Authentication

To access the GitHub API with authentication—which is essential for decent rate limits and accessing private repositories—you need a Personal Access Token (PAT). This token works like a temporary password with a limited scope, allowing your MCP Server to access the API on your behalf without needing your actual password.

💎 Core Concept

To create a Personal Access Token: go to GitHub Settings → Developer settings → Personal access tokens → Fine-grained tokens. Select the required scopes—for our project, we need access to repos e issues. The generated token must be stored as an environment variable (e.g., GITHUB_TOKEN) and passed through HTTP headers as Authorization: Bearer {token}. Never put the token directly in the source code.

🚨 Alert

NEVER commit tokens to the repository! If you accidentally push a token to GitHub, GitHub Secret Scanning will automatically revoke it—but the damage may already be done. Always use environment variables, add .env to .gitignore, and never log the token value in server outputs.

❌ Avoid
  • •Token hardcoded in the source code
  • •Commit the .env file to the repository
  • •Token with excessive scopes (admin)
  • •Token with no expiration date
✅ Do
  • •Store token in an environment variable
  • •Add .env to .gitignore
  • •Use the minimum necessary scopes
  • •Set an expiration date for the token
3

📋 Tool: List Repositories

The first tool in our GitHub MCP Server will list repositories. Using the endpoint GET /user/repos, we'll fetch the authenticated user's repositories, filter by visibility, sort by last updated, and format the results in a readable way for Claude to present to the user.

💎 Core Concept

The repository listing tool uses the library httpx to make asynchronous requests to the GitHub API. The essential code:

@server.tool()
async def list_repos(visibility: str = "all") -> str:
    """Lista repositórios do usuário autenticado."""
    async with httpx.AsyncClient() as client:
        resp = await client.get(
            "https://api.github.com/user/repos",
            headers={"Authorization": f"Bearer {GITHUB_TOKEN}"},
            params={"visibility": visibility, "sort": "updated"}
        )
        repos = resp.json()
    lines = []
    for r in repos:
        stars = r["stargazers_count"]
        lang = r.get("language", "N/A")
        lines.append(f"⭐ {stars} | {r['name']} ({lang}) — {r.get('description', 'Sem descrição')}")
    return "\n".join(lines)
💡 Practical Tip

Format the output so that the Claude can interpret easily. Include the name, description, primary language, and star count. Use optional parameters such as visibility (all, public, private) to give Claude flexibility in its queries. The parameter sort=updated ensures the most recent repos appear first.

📋 list_repos Tool Flow
Step 1

Claude receives the user's request ("list my repos") and calls the list_repos tool with the appropriate parameters.

Step 2

The MCP Server sends GET /user/repos with the authentication token and receives the list of repositories in JSON.

Step 3

The tool formats the data (name, description, language, stars) and returns structured text for Claude to present.

4

🔍 Tool: Search Issues

The second tool lets you search for issues in a specific repository. Using the endpoint GET /repos/{owner}/{repo}/issues, Claude will be able to check open or closed issues, filter by labels, and present relevant information such as number, title, author, and assigned labels.

💎 Core Concept

The issue search tool receives owner (repository owner), repo (repository name) and optional parameters such as state (open/closed/all) and labels (filter by labels). The endpoint returns a JSON array where each issue contains: number, title, user.login, state, labels[] e created_at. Format the output with the number, title, author, and labels to make it easier to read.

❌ Avoid
  • •Return raw JSON to Claude
  • •Ignore handling for a nonexistent repo
  • •Don't filter — return hundreds of issues
  • •Mixing issues and pull requests (PRs come from the same endpoint)
✅ Do
  • •Format output with #number, title, and author
  • •Handle 404 errors with a clear message
  • •Limit results (e.g., max 30 issues)
  • •Filter PRs by checking the "pull_request" field
📊 Data and Research

One important detail: the endpoint /repos/{owner}/{repo}/issues returns both issues and pull requests. To filter only real issues, check whether the field pull_request is missing from the object. The API supports pagination via parameters page e per_page (maximum 100 per page).

5

➕ Tool: Create Issue

The third tool is the most powerful—and the most dangerous. Using POST /repos/{owner}/{repo}/issues, Claude will be able to create issues directly in your repository. This is a write action, which requires careful validation of parameters and awareness that we’re giving the model the ability to modify real data.

💎 Core Concept

The issue creation tool sends a POST with a JSON body containing title (required) and body (description, optional but recommended). Validate that the title is not empty and has a reasonable length. After creating it, return the URL of the created issue so the user can access it directly:

@server.tool()
async def create_issue(owner: str, repo: str, title: str, body: str = "") -> str:
    """Cria uma nova issue em um repositório GitHub."""
    if not title.strip():
        return "Erro: o título da issue não pode ser vazio."
    async with httpx.AsyncClient() as client:
        resp = await client.post(
            f"https://api.github.com/repos/{owner}/{repo}/issues",
            headers={"Authorization": f"Bearer {GITHUB_TOKEN}"},
            json={"title": title, "body": body}
        )
    if resp.status_code == 201:
        issue = resp.json()
        return f"Issue criada com sucesso! #{issue['number']}: {issue['html_url']}"
    return f"Erro ao criar issue: {resp.status_code}"
🚨 Alert — Destructive Action

Creating issues is a write action — it modifies real data in your GitHub. Unlike listing or searching, this tool has permanent side effects. Consider adding a confirmation before creating an issue, or at least logging each issue created. In a corporate environment, this tool should have strict access control and a complete audit trail.

💡 Practical Tip

Always return the URL of the created issue in the response—this allows Claude to show the user a clickable link. Validate the parameters before sending the request: nonempty title, and owner and repo in the correct format. Consider adding optional parameters such as labels e assignees to create more complete issues.

6

🧪 Testing the Integration

With the three tools implemented—list repos, search issues, and create issues—it's time to test the complete integration. Configure the server in Claude Desktop and try natural-language commands to verify that everything works correctly. Claude should be able to automatically choose the right tool based on your request.

💎 Core Concept

Test with these prompts in Claude Desktop:

  • →"List my repositories" — should call list_repos and display name, language, stars
  • →"Which issues are open in repo X?" — should call search_issues with state=open
  • →"Create an issue in repo Y with title Z" — should call create_issue and return the URL
  • →"Which private repos do I have?" — should call list_repos with visibility=private
📋 Complete Test Scenario
Test 1 — Read

Ask Claude to list your repositories. Check whether the data matches what appears in the GitHub web interface.

Test 2 — Query

Ask it to search for issues in a real repository. Confirm that the numbers, titles, and labels are correct.

Test 3 — Write

Ask it to create a test issue. Check on GitHub that the issue was created with the correct title and body.

Test 4 — Error

Ask it to search for issues in a repository that doesn't exist. Check whether the error is handled gracefully.

❌ Avoid
  • •Test only the “happy path”
  • •Ignore error scenarios (invalid repo, no permission)
  • •Don't check whether the returned data is correct
  • •Create test issues in production repos
✅ Do
  • •Test all scenarios (success + error)
  • •Check data against the web interface
  • •Use a test repository to create issues
  • •Test with different parameters (filters, visibility)

📝 Module Summary

  • ✓ The GitHub REST API at api.github.com provides endpoints for repositories, issues, and much more, returning JSON.
  • ✓ Authentication via a Personal Access Token stored in an environment variable — never in the code.
  • ✓ The list_repos tool uses GET /user/repos to list repositories with their name, description, language, and stars.
  • ✓ The search_issues tool uses GET /repos/{owner}/{repo}/issues to search and filter issues by state and labels.
  • ✓ The create_issue tool uses POST to create issues, validates parameters, and returns the URL of the created issue.
  • ✓ Tests should cover success and error scenarios, using test repositories for write actions.

Next Module: 4.5 — Combining Multiple Servers